Compare commits

...

861 Commits
v0.13 ... v5.1

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

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

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

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

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

View File

@@ -1,16 +1,39 @@
[bumpversion]
commit = True
tag = True
current_version = 0.13
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
current_version = 5.1
parse = (?P<major>\d+)\.(?P<minor>\d+)
serialize =
{major}.{minor}
[bumpversion:file:src/config.h]
search = VERSION "{current_version}"
replace = VERSION "{new_version}"
[bumpversion:file:src/libs/config.h]
parse = (?P<major>\d+)
serialize = {major}
search = VERSION_MAJOR {current_version}
replace = VERSION_MAJOR {new_version}
[bumpversion:file:PKGBUILD]
[bumpversion:file:./src/libs/config.h]
parse = <major>\d+\.(?P<minor>\d+)
serialize = {minor}
search = VERSION_MINOR {current_version}
replace = VERSION_MINOR {new_version}
[bumpversion:file:python/setup.py]
search = version="{current_version}"
replace = version="{new_version}"
[bumpversion:file:pkg/arch/PKGBUILD]
search = pkgver={current_version}
replace = pkgver={new_version}
[bumpversion:file:pkg/openwrt/Makefile]
search = PKG_VERSION:={current_version}
replace = PKG_VERSION:={new_version}
[bumpversion:file:man/ustreamer.1]
search = "version {current_version}"
replace = "version {new_version}"
[bumpversion:file:man/ustreamer-dump.1]
search = "version {current_version}"
replace = "version {new_version}"

9
.dockerignore Normal file
View File

@@ -0,0 +1,9 @@
# Ignore everything
*
# Allow source code
!Makefile
!src/**
!python/**
!janus/**
!man/**

10
.editorconfig Normal file
View File

@@ -0,0 +1,10 @@
root = true
[*]
end_of_file = lf
indent_style = tab
indent_size = 4
[*.{py,yaml}]
indent_style = space
indent_size = 4

5
.gitattributes vendored Normal file
View File

@@ -0,0 +1,5 @@
/.github/ export-ignore
/.bumpversion.cfg export-ignore
/.dockerignore export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore

4
.github/FUNDING.yml vendored Normal file
View File

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

20
.github/workflows/dockerimage.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
name: CI
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Building linters image ...
run: make linters
- name: Running linters ...
run: make tox

23
.gitignore vendored
View File

@@ -1,8 +1,17 @@
/pkg/
/src/ustreamer-*/
/src/v*.tar.gz
/v*.tar.gz
/ustreamer-*.pkg.tar.xz
/vgcore.*
/linters/.tox/
/linters/.mypy_cache/
/pkg/arch/pkg/
/pkg/arch/src/
/src/build/
/python/build/
/janus/build/
/ustreamer
*.o
/ustreamer-dump
/config.mk
vgcore.*
*.sock
*.so
*.bin
*.pkg.tar.xz
*.pkg.tar.zst
*.tar.gz

127
Makefile
View File

@@ -1,55 +1,108 @@
-include config.mk
DESTDIR ?=
PREFIX ?= /usr/local
MANPREFIX ?= $(PREFIX)/share/man
CC ?= gcc
PY ?= python3
CFLAGS ?= -O3
LDFLAGS ?=
RPI_VC_HEADERS ?= /opt/vc/include
RPI_VC_LIBS ?= /opt/vc/lib
export
_LINTERS_IMAGE ?= ustreamer-linters
# =====
CC = gcc
LIBS = -lm -ljpeg -pthread -levent -levent_pthreads
override CFLAGS += -c -std=c99 -Wall -Wextra -D_GNU_SOURCE
SOURCES = $(shell ls src/*.c src/jpeg/*.c)
OBJECTS = $(SOURCES:.c=.o)
PROG = ustreamer
define optbool
$(filter $(shell echo $(1) | tr A-Z a-z), yes on 1)
endef
ifeq ($(shell ls -d /opt/vc/include 2>/dev/null), /opt/vc/include)
SOURCES += $(shell ls src/omx/*.c)
LIBS += -lbcm_host -lvcos -lopenmaxil -L/opt/vc/lib
override CFLAGS += -DOMX_ENCODER -DOMX_SKIP64BIT -I/opt/vc/include
# =====
all:
+ $(MAKE) apps
ifneq ($(call optbool,$(WITH_PYTHON)),)
+ $(MAKE) python
endif
ifneq ($(call optbool,$(WITH_JANUS)),)
+ $(MAKE) janus
endif
# =====
all: $(SOURCES) $(PROG)
apps:
$(MAKE) -C src
@ ln -sf src/ustreamer.bin ustreamer
@ ln -sf src/ustreamer-dump.bin ustreamer-dump
install: $(PROG)
install -Dm755 $(PROG) $(DESTDIR)$(PREFIX)/bin/$(PROG)
python:
$(MAKE) -C python
@ ln -sf python/build/lib.*/*.so .
janus:
$(MAKE) -C janus
@ ln -sf janus/*.so .
install: all
$(MAKE) -C src install
ifneq ($(call optbool,$(WITH_PYTHON)),)
$(MAKE) -C python install
endif
ifneq ($(call optbool,$(WITH_JANUS)),)
$(MAKE) -C janus install
endif
mkdir -p $(DESTDIR)$(MANPREFIX)/man1
for man in $(shell ls man); do \
install -m644 man/$$man $(DESTDIR)$(MANPREFIX)/man1/$$man; \
gzip -f $(DESTDIR)$(MANPREFIX)/man1/$$man; \
done
install-strip: install
$(MAKE) -C src install-strip
regen:
tools/make-jpg-h.py src/data/blank.jpg src/data/blank.h BLANK 640 480
$(PROG): $(OBJECTS)
$(CC) $(LIBS) $(LDFLAGS) $(OBJECTS) -o $@
.c.o:
$(CC) $(LIBS) $(CFLAGS) $< -o $@
tools/$(MAKE)-jpeg-h.py src/ustreamer/data/blank.jpeg src/ustreamer/data/blank_jpeg.c BLANK
tools/$(MAKE)-html-h.py src/ustreamer/data/index.html src/ustreamer/data/index_html.c INDEX
release:
make clean
make push
make bump
make push
make clean
$(MAKE) clean
$(MAKE) tox
$(MAKE) push
$(MAKE) bump V=$(V)
$(MAKE) push
$(MAKE) clean
tox: linters
time docker run --rm \
--volume `pwd`:/src:ro \
--volume `pwd`/linters:/src/linters:rw \
-t $(_LINTERS_IMAGE) bash -c " \
cd /src \
&& tox -q -c linters/tox.ini $(if $(E),-e $(E),-p auto) \
"
linters:
docker build \
$(if $(call optbool,$(NC)),--no-cache,) \
--rm \
--tag $(_LINTERS_IMAGE) \
-f linters/Dockerfile linters
bump:
bumpversion minor
bumpversion $(if $(V),$(V),minor)
push:
@@ -57,6 +110,18 @@ push:
git push --tags
clean-all: linters clean
- docker run --rm \
--volume `pwd`:/src \
-it $(_LINTERS_IMAGE) bash -c "cd src && rm -rf linters/{.tox,.mypy_cache}"
clean:
rm -f src/*.o src/{jpeg,omx}/*.o vgcore.* $(PROG)
rm -rf pkg src/$(PROG)-* src/v*.tar.gz v*.tar.gz $(PROG)-*.pkg.tar.xz
rm -rf pkg/arch/pkg pkg/arch/src pkg/arch/v*.tar.gz pkg/arch/ustreamer-*.pkg.tar.{xz,zst}
rm -f ustreamer ustreamer-dump *.so
$(MAKE) -C src clean
$(MAKE) -C python clean
$(MAKE) -C janus clean
.PHONY: python janus linters

View File

@@ -1,30 +0,0 @@
# Contributor: Maxim Devaev <mdevaev@gmail.com>
# Author: Maxim Devaev <mdevaev@gmail.com>
pkgname=ustreamer
pkgver=0.13
pkgrel=1
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
url="https://github.com/pi-kvm/ustreamer"
license=(GPL)
arch=(i686 x86_64 armv6h armv7h)
depends=(libjpeg libevent)
# optional: raspberrypi-firmware for OMX JPEG compressor
makedepends=(gcc make)
source=("$url/archive/v$pkgver.tar.gz")
md5sums=(SKIP)
build() {
cd $srcdir
rm -rf $pkgname-build
cp -r ustreamer-$pkgver $pkgname-build
cd $pkgname-build
make CFLAGS="$CFLAGS" LDFLAGS="$LDFLAGS" $MAKEFLAGS
}
package() {
cd $srcdir/$pkgname-build
make DESTDIR="$pkgdir" PREFIX=/usr install
}

132
README.md
View File

@@ -1,69 +1,133 @@
# µStreamer
[![CI](https://github.com/pikvm/ustreamer/workflows/CI/badge.svg)](https://github.com/pikvm/ustreamer/actions?query=workflow%3ACI)
[![Discord](https://img.shields.io/discord/580094191938437144?logo=discord)](https://discord.gg/bpmXfz5)
µStreamer - это маленький и очень быстрый сервер, который позволяет организовать трансляцию видео в формате [MJPG](https://en.wikipedia.org/wiki/Motion_JPEG) с любого устройства V4L2 в сеть. Этот формат нативно поддерживается всеми современными браузерами и большинством приложений для просмотра видео (mplayer, VLC и так далее). µStreamer был разработан в рамках проекта [Pi-KVM](https://github.com/pi-kvm) специально для стриминга с устройств видеозахвата [VGA](https://www.amazon.com/dp/B0126O0RDC) и [HDMI](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/) с максимально возможным разрешением и FPS, которые только позволяет железо.
[[Русская версия]](README.ru.md)
Функционально µStreamer очень похож на [mjpg-streamer](https://github.com/jacksonliam/mjpg-streamer) при использовании им плагинов ```input_uvc.so``` и ```output_http.so```, однако имеет ряд серьезных отличий. Основные приведены в этой таблице:
µStreamer is a lightweight and very quick server to stream [MJPEG](https://en.wikipedia.org/wiki/Motion_JPEG) video from any V4L2 device to the net. All new browsers have native support of this video format, as well as most video players such as mplayer, VLC etc.
µStreamer is a part of the [Pi-KVM](https://github.com/pikvm/pikvm) project designed to stream [VGA](https://www.amazon.com/dp/B0126O0RDC) and [HDMI](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/) screencast hardware data with the highest resolution and FPS possible.
| **Фича** | **µStreamer** | **mjpg-streamer** |
µStreamer is very similar to [mjpg-streamer](https://github.com/jacksonliam/mjpg-streamer) with ```input_uvc.so``` and ```output_http.so``` plugins, however, there are some major differences. The key ones are:
| **Feature** | **µStreamer** | **mjpg-streamer** |
|----------|---------------|-------------------|
| Многопоточное кодирование JPEG | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Аппаратное кодирование с помощью [OpenMAX IL](https://www.khronos.org/openmaxil) на Raspberry Pi | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Поведение при физическом отключении устройства<br>от сервера во время работы | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Транслирует черный экран<br>с надписью ```NO SIGNAL```,<br>пока устройство не будет подключено снова | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Необратимо зависает <sup>1</sup> |
| Поддержка [DV-таймингов](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) - возможности изменения <br>параметров разрешения трансляции на лету<br>по сигналу источника (устройства видеозахвата) | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет <sup>1</sup> |
| Возможность пропуска фреймов при передаче<br>статического изображения по HTTP<br>для экономии трафика | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть <sup>2</sup> | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Дебаг-логи без перекомпиляции,<br>логгирование статистики производительности,<br>возможность получения параметров<br>трансляции по HTTP | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Поддерживаемые входные форматы устройств | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) YUYV, UYVY,<br>RGB565, ~~MJPG~~ <sup>3</sup> | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) YUYV, UYVY,<br>RGB565, MJPG |
| Поддержка контролов веб-камер (фокус,<br> движение сервами) и всяких настроек,<br> типа яркости, через HTTP | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть |
| Возможность сервить файлы встроенным<br>HTTP-сервером, настройки авторизации | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет <sup>4</sup> | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть |
| Multithreaded JPEG encoding | ✔ | ✘ |
| Hardware image encoding<br>on Raspberry Pi | ✔ | ✘ |
| Behavior when the device<br>is disconnected while streaming | ✔ Shows a black screen<br>with ```NO SIGNAL``` on it<br>until reconnected | ✘ Stops the streaming <sup>1</sup> |
| [DV-timings](https://linuxtv.org/downloads/v4l-dvb-apis-new/userspace-api/v4l/dv-timings.html) support -<br>the ability to change resolution<br>on the fly by source signal | ✔ | ☹ Partially yes <sup>1</sup> |
| Option to skip frames when streaming<br>static images by HTTP to save the traffic | ✔ <sup>2</sup> | ✘ |
| Streaming via UNIX domain socket | ✔ | ✘ |
| Systemd socket activation | ✔ | ✘ |
| Debug logs without recompiling,<br>performance statistics log,<br>access to HTTP streaming parameters | ✔ | ✘ |
| Option to serve files<br>with a built-in HTTP server | ✔ | ☹ Regular files only |
| Signaling about the stream state<br>on GPIO using [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) | ✔ | ✘ |
| Access to webcam controls (focus, servos)<br>and settings such as brightness via HTTP | ✘ | ✔ |
| Compatibility with mjpg-streamer's API | ✔ | :) |
Сносочки:
* ```1``` Для mjpg-streamer существует [мой патч](https://github.com/jacksonliam/mjpg-streamer/pull/164), предотвращающий зависание при отключении устройства и добавляющий поддержку DV-таймингов, однако трансляция при этом все равно прерывается. В данный момент этот патч не принят в апстрим, и я даже не гарантирую его стопроцентную работоспособность. Код mjpg-streamer очень плохо структурирован и чрезвычайно запутан, и я мог что-то упустить. Собственно, это одна из причин, почему µStreamer написан с нуля.
* ```2``` Это фича позволяет в несколько раз снизить объем исходящего трафика при трансляции HDMI, однако увеличивает загрузку процессора и добавляет небольшую задержку. Суть в том, что HDMI - полностью цифровой интерфейс, и новый захваченный фрейм может быть идентичен предыдущему в точности до байта. В этом случае нет нужды передавать одну и ту же картинку по сети несколько раз в секунду. При использовании опции `--drop-same-frames=20`, µStreamer будет дропать все одинаковые фреймы, но не более 20. Новый фрейм сравнивается с предыдущим сначала по длине, а затем помощью ```memcmp()```.
Footnotes:
* ```1``` Long before µStreamer, I made a [patch](https://github.com/jacksonliam/mjpg-streamer/pull/164) to add DV-timings support to mjpg-streamer and to keep it from hanging up no device disconnection. Alas, the patch is far from perfect and I can't guarantee it will work every time - mjpg-streamer's source code is very complicated and its structure is hard to understand. With this in mind, along with needing multithreading and JPEG hardware acceleration in the future, I decided to make my own stream server from scratch instead of supporting legacy code.
* ```3``` Поскольку µStreamer писался в первую очередь для устройств видеозахвата, в нем реализованы только те форматы, которые для них были нужны. MJPG в контексте входных данных означает, что устройство умеет самостоятельно сжимать картинку в JPEG и отдавать ее программе, что позволяет значительно снизить загрузку процессора и избавить его от необходимости кодировать картинку софтом. Этот формат поддерживается большинством веб-камер, но не поддерживается ни одним из встреченных мной устройств видеозахвата; его не умеет ни [Auvidea B101](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/), ни [EasyCap UTV 007](https://www.amazon.com/dp/B0126O0RDC). Нет никаких технических сложностей добавить поддержку аппаратного MJPG источника, но у меня просто пока не дошли до этого руки.
* ```4``` ... и не будет. µStreamer придерживается концепции UNIX-way, так что если вам нужно нарисовать маленький сайтик со встроенной трансляцией - просто поставьте NGINX.
* ```2``` This feature allows to cut down outgoing traffic several-fold when streaming HDMI, but it increases CPU usage a little bit. The idea is that HDMI is a fully digital interface and each captured frame can be identical to the previous one byte-wise. There's no need to stream the same image over the net several times a second. With the `--drop-same-frames=20` option enabled, µStreamer will drop all the matching frames (with a limit of 20 in a row). Each new frame is matched with the previous one first by length, then using ```memcmp()```.
-----
# TL;DR
Если вам нужно вещать стрим с уличной камеры и управлять ее параметрами - возьмите mjpg-streamer. Если же вам нужно очень качественное изображение с высоким FPS - µStreamer ваш бро.
If you're going to live-stream from your backyard webcam and need to control it, use mjpg-streamer. If you need a high-quality image with high FPS - µStreamer for the win.
-----
# Сборка
Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads``` и ```libjpeg8```/```libjpeg-turbo```.
# Building
You'll need ```make```, ```gcc```, ```libevent``` with ```pthreads``` support, ```libjpeg9```/```libjpeg-turbo``` and ```libbsd``` (only for Linux).
На Raspberry Pi програма автоматически собирается с поддержкой OpenMAX, если обнаружит нужные хедеры в ```/opt/vc/include```.
* Arch: `sudo pacman -S libevent libjpeg-turbo libutil-linux libbsd`.
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev libbsd-dev`. Add `libgpiod-dev` for `WITH_GPIO=1` and `libsystemd-dev` for `WITH_SYSTEMD=1`.
* Debian/Ubuntu: `sudo apt install build-essential libevent-dev libjpeg-dev libbsd-dev`.
To enable GPIO support install [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) and pass option ```WITH_GPIO=1```. If the compiler reports about a missing function ```pthread_get_name_np()``` (or similar), add option ```WITH_PTHREAD_NP=0``` (it's enabled by default). For the similar error with ```setproctitle()``` add option ```WITH_SETPROCTITLE=0```.
```
$ git clone --depth=1 https://github.com/pi-kvm/ustreamer
$ git clone --depth=1 https://github.com/pikvm/ustreamer
$ cd ustreamer
$ make
$ ./ustreamer --help
```
Для Arch Linux в AUR есть готовый пакет: https://aur.archlinux.org/packages/ustreamer
AUR has a package for Arch Linux: https://aur.archlinux.org/packages/ustreamer.
FreeBSD port: https://www.freshports.org/multimedia/ustreamer.
-----
# Использование
Будучи запущенным без аргументов, ```ustremaer``` попробует открыть устройство ```/dev/video0``` с разрешением 640x480 и начать трансляцию на ```http://localhost:8080```. Это поведение может быть изменено с помощью опций ```--device```, ```--host``` и ```--port```. Пример вещания на всю сеть по 80 порту:
# Usage
Without arguments, ```ustreamer``` will try to open ```/dev/video0``` with 640x480 resolution and start streaming on ```http://127.0.0.1:8080```. You can override this behavior using parameters ```--device```, ```--host``` and ```--port```. For example, to stream to the world, run:
```
# ./ustreamer --device=/dev/video1 --host=0.0.0.0 --port=80
```
Рекомендуемый способ запуска µStreamer для работы с [Auvidea B101](https://www.raspberrypi.org/forums/viewtopic.php?f=38&t=120702&start=400#p1339178) на Raspberry Pi:
:exclamation: Please note that since µStreamer v2.0 cross-domain requests were disabled by default for [security reasons](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). To enable the old behavior, use the option `--allow-origin=\*`.
The recommended way of running µStreamer with [Auvidea B101](https://www.raspberrypi.org/forums/viewtopic.php?f=38&t=120702&start=400#p1339178) on Raspberry Pi:
```bash
$ ./ustreamer \
--format=uyvy \ # Настройка входного формата устройства
--encoder=omx \ # Использование аппаратного кодирования с помощью OpenMAX
--dv-timings # Включение DV-таймингов
--format=uyvy \ # Device input format
--encoder=m2m-image \ # Hardware encoding on V4L2 M2M driver
--workers=3 \ # Workers number
--persistent \ # Don't re-initialize device on timeout (for example when HDMI cable was disconnected)
--dv-timings \ # Use DV-timings
--drop-same-frames=30 # Save the traffic
```
За полным списком опций обращайтесь ко встроенной справке: ```ustreamer --help```.
:exclamation: Please note that to use `--drop-same-frames` for different browsers you need to use some specific URL `/stream` parameters (see URL `/` for details).
You can always view the full list of options with ```ustreamer --help```.
-----
# Лицензия
Copyright (C) 2018 by Maxim Devaev mdevaev@gmail.com
# Raspberry Pi Camera Example
Example usage for the Raspberry Pi v1 camera:
```bash
$ sudo modprobe bcm2835-v4l2
$ ./ustreamer --host :: -m jpeg --device-timeout=5 --buffers=3 -r 2592x1944
```
:exclamation: Please note that newer camera models have a different maximum resolution. You can see the supported resolutions at the [PiCamera documentation](https://picamera.readthedocs.io/en/release-1.13/fov.html#sensor-modes).
:exclamation: If you get a poor framerate, it could be that the camera is switched to photo mode, which produces a low framerate (but a higher quality picture). This is because `bcm2835-v4l2` switches to photo mode at resolutions higher than `1280x720`. To work around this, pass the `max_video_width` and `max_video_height` module parameters like so:
```bash
$ modprobe bcm2835-v4l2 max_video_width=2592 max_video_height=1944
```
-----
# Integrations
## Nginx
When uStreamer is behind an Nginx proxy, it's buffering behavior introduces latency into the video stream. It's possible to disable Nginx's buffering to eliminate the additional latency:
```nginx
location /stream {
postpone_output 0;
proxy_buffering off;
proxy_ignore_headers X-Accel-Buffering;
proxy_pass http://ustreamer;
}
```
-----
# Tips & tricks for v4l2
v4l2 utilities provide the tools to manage USB webcam setting and information. Scripts can be use to make adjustments and run manually or with cron. Running in cron for example to change the exposure settings at certain times of day. The package is available in all Linux distributions and is usually called `v4l-utils`.
* List of available video devices: `v4l2-ctl --list-devices`.
* List available control settings: `v4l2-ctl -d /dev/video0 --list-ctrls`.
* List available video formats: `v4l2-ctl -d /dev/video0 --list-formats-ext`.
* Read the current setting: `v4l2-ctl -d /dev/video0 --get-ctrl=exposure_auto`.
* Change the setting value: `v4l2-ctl -d /dev/video0 --set-ctrl=exposure_auto=1`.
[Here](https://www.kurokesu.com/main/2016/01/16/manual-usb-camera-settings-in-linux/) you can find more examples. Documentation is available in [`man v4l2-ctl`](https://www.mankier.com/1/v4l2-ctl).
-----
# See also
* [Running uStreamer via systemd service](https://github.com/pikvm/ustreamer/issues/16).
-----
# License
Copyright (C) 2018-2022 by Maxim Devaev mdevaev@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

143
README.ru.md Normal file
View File

@@ -0,0 +1,143 @@
# µStreamer
[![CI](https://github.com/pikvm/ustreamer/workflows/CI/badge.svg)](https://github.com/pikvm/ustreamer/actions?query=workflow%3ACI)
[![Discord](https://img.shields.io/discord/580094191938437144?logo=discord)](https://discord.gg/bpmXfz5)
[[English version]](README.md)
µStreamer - это маленький и очень быстрый сервер, который позволяет организовать трансляцию видео в формате [MJPEG](https://en.wikipedia.org/wiki/Motion_JPEG) с любого устройства V4L2 в сеть. Этот формат нативно поддерживается всеми современными браузерами и большинством приложений для просмотра видео (mplayer, VLC и так далее). µStreamer был разработан в рамках проекта [Pi-KVM](https://github.com/pikvm/pikvm) специально для стриминга с устройств видеозахвата [VGA](https://www.amazon.com/dp/B0126O0RDC) и [HDMI](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/) с максимально возможным разрешением и FPS, которые только позволяет железо.
Функционально µStreamer очень похож на [mjpg-streamer](https://github.com/jacksonliam/mjpg-streamer) при использовании им плагинов ```input_uvc.so``` и ```output_http.so```, однако имеет ряд серьезных отличий. Основные приведены в этой таблице:
| **Фича** | **µStreamer** | **mjpg-streamer** |
|----------|---------------|-------------------|
| Многопоточное кодирование JPEG | ✔ | ✘ |
| Аппаратное кодирование на Raspberry Pi | ✔ | ✘ |
| Поведение при физическом отключении<br>устройства от сервера во время работы | ✔ Транслирует черный экран<br>с надписью ```NO SIGNAL```,<br>пока устройство не будет подключено снова | ✘ Прерывает трансляцию <sup>1</sup> |
| Поддержка [DV-таймингов](https://linuxtv.org/downloads/v4l-dvb-apis-new/userspace-api/v4l/dv-timings.html) - возможности<br>изменения параметров разрешения<br>трансляции на лету по сигналу<br>источника (устройства видеозахвата) | ✔ | ☹ Условно есть <sup>1</sup> |
| Возможность пропуска фреймов при передаче<br>статического изображения по HTTP<br>для экономии трафика | ✔ <sup>2</sup> | ✘ |
| Стрим через UNIX domain socket | ✔ | ✘ |
| Systemd socket activation | ✔ | ✘ |
| Дебаг-логи без перекомпиляции,<br>логгирование статистики производительности,<br>возможность получения параметров<br>трансляции по HTTP | ✔ | ✘ |
| Возможность сервить файлы встроенным<br>HTTP-сервером | ✔ | ☹ Нет каталогов |
| Вывод сигналов о состоянии стрима на GPIO<br>с помощью [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) | ✔ | ✘ |
| Поддержка контролов веб-камер (фокус,<br> движение сервами) и всяких настроек,<br> типа яркости, через HTTP | ✘ | ✔ |
| Совместимость с API mjpg-streamer'а | ✔ | :) |
Сносочки:
* ```1``` Еще до написания µStreamer, я запилил [патч](https://github.com/jacksonliam/mjpg-streamer/pull/164), добавляющий в mjpg-streamer поддержку DV-таймингов и предотвращающий его зависание при отключении устройства. Однако патч, увы, далек от совершенства и я не гарантирую его стопроцентную работоспособность, поскольку код mjpg-streamer чрезвычайно запутан и очень плохо структурирован. Учитывая это, а также то, что в дальнейшем мне потребовались многопоточность и аппаратное кодирование JPEG, было принято решение написать свой стрим-сервер с нуля, чтобы не тратить силы на поддержку лишнего легаси.
* ```2``` Это фича позволяет в несколько раз снизить объем исходящего трафика при трансляции HDMI, однако немного увеличивает загрузку процессора. Суть в том, что HDMI - полностью цифровой интерфейс, и новый захваченный фрейм может быть идентичен предыдущему в точности до байта. В этом случае нет нужды передавать одну и ту же картинку по сети несколько раз в секунду. При использовании опции `--drop-same-frames=20`, µStreamer будет дропать все одинаковые фреймы, но не более 20 подряд. Новый фрейм сравнивается с предыдущим сначала по длине, а затем помощью ```memcmp()```.
-----
# TL;DR
Если вам нужно вещать стрим с уличной камеры и управлять ее параметрами - возьмите mjpg-streamer. Если же вам нужно очень качественное изображение с высоким FPS - µStreamer ваш бро.
-----
# Сборка
Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads```, ```libjpeg8```/```libjpeg-turbo``` и ```libbsd``` (только для Linux).
* Arch: `sudo pacman -S libevent libjpeg-turbo libutil-linux libbsd`.
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev libbsd-dev`. Добавьте `libgpiod` для `WITH_GPIO=1` и `libsystemd-dev` для `WITH_SYSTEMD=1`.
* Debian/Ubuntu: `sudo apt install build-essential libevent-dev libjpeg-dev libbsd-dev`.
Для включения сборки с поддержкой GPIO установите [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) и добавьте параметр ```WITH_GPIO=1```. Если при сборке компилятор ругается на отсутствие функции ```pthread_get_name_np()``` или другой подобной, добавьте параметр ```WITH_PTHREAD_NP=0``` (по умолчанию он включен). При аналогичной ошибке с функцией ```setproctitle()``` добавьте параметр ```WITH_SETPROCTITLE=0```.
```
$ git clone --depth=1 https://github.com/pikvm/ustreamer
$ cd ustreamer
$ make
$ ./ustreamer --help
```
Для Arch Linux в AUR есть готовый пакет: https://aur.archlinux.org/packages/ustreamer.
Порт для FreeBSD: https://www.freshports.org/multimedia/ustreamer.
-----
# Использование
Будучи запущенным без аргументов, ```ustreamer``` попробует открыть устройство ```/dev/video0``` с разрешением 640x480 и начать трансляцию на ```http://127.0.0.1:8080```. Это поведение может быть изменено с помощью опций ```--device```, ```--host``` и ```--port```. Пример вещания на всю сеть по 80-м порту:
```
# ./ustreamer --device=/dev/video1 --host=0.0.0.0 --port=80
```
:exclamation: Обратите внимание, что начиная с версии µStreamer v2.0 кросс-доменные запросы были выключены по умолчанию [по соображениям безопасности](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). Чтобы включить старое поведение, используйте опцию `--allow-origin=\*`.
Рекомендуемый способ запуска µStreamer для работы с [Auvidea B101](https://www.raspberrypi.org/forums/viewtopic.php?f=38&t=120702&start=400#p1339178) на Raspberry Pi:
```bash
$ ./ustreamer \
--format=uyvy \ # Настройка входного формата устройства
--encoder=m2m-image \ # Аппаратное кодирование с помощью драйвера V4L2 M2M
--workers=3 \ # Максимум воркеров
--persistent \ # Не переинициализировать устройство при таймауте (например, когда был отключен HDMI-кабель)
--dv-timings \ # Включение DV-таймингов
--drop-same-frames=30 # Экономим трафик
```
:exclamation: Обратите внимание, что для использования `--drop-same-frames` для разных браузеров нужно использовать ряд специальных параметров в `/stream` (за деталями обратитесь к урлу `/`).
За полным списком опций обращайтесь ко встроенной справке: ```ustreamer --help```.
-----
# Камера Raspberry Pi
Пример использования камеры Raspberry Pi v1:
```bash
$ sudo modprobe bcm2835-v4l2
$ ./ustreamer --host :: -m jpeg --device-timeout=5 --buffers=3 -r 2592x1944
```
:exclamation: Обратите внимание что боле новые модели камеры имеют другое максимальное разрешение. Список поддерживаемых разрешений можно найти в [документации PiCamera](https://picamera.readthedocs.io/en/release-1.13/fov.html#sensor-modes).
:exclamation: Если камера выдает низкий фреймрейт, возможно что она работает в фото-режиме, где производит более низкий фпс, но более качественную кратинку. Это происходит потому что `bcm2835-v4l2` переключает камеру в фото-режим на разрешениях выше `1280x720`. Чтобы обойти это, передайте параметры `max_video_width` и `max_video_height` при загрузке модуля, например:
```bash
$ modprobe bcm2835-v4l2 max_video_width=2592 max_video_height=1944
```
-----
# Интеграция
## Nginx
Если uStreamer находится на Nginx, то последний будет буферизировать поток и создавать дополнительную задержку в стриме. Чтобы задержки не было, буферизацию можно отключить:
```nginx
location /stream {
postpone_output 0;
proxy_buffering off;
proxy_ignore_headers X-Accel-Buffering;
proxy_pass http://ustreamer;
}
```
-----
# Утилиты V4L2
V4L2 предоставляет ряд официальных утилит для управления USB-вебкамерами и получения информации об устройствах. С их помощью можно писать всякие настроечные скрипты и запускать их по крону, если, например, вам требуется изменять настройки экспозиции в зависимости от времени суток. Пакет с этими утилитами доступен на всех дистрибутивах Linux и обычно называется `v4l-utils`.
* Вывести список видеоустройств: `v4l2-ctl --list-devices`.
* Вывести список доступных контролов устройства: `v4l2-ctl -d /dev/video0 --list-ctrls`.
* Вывести список доступных форматов видео: `v4l2-ctl -d /dev/video0 --list-formats-ext`.
* Показать текущее значение контрола: `v4l2-ctl -d /dev/video0 --get-ctrl=exposure_auto`.
* Изменить значение контрола: `v4l2-ctl -d /dev/video0 --set-ctrl=exposure_auto=1`.
Больше примеров вы можете найти [здесь](https://www.kurokesu.com/main/2016/01/16/manual-usb-camera-settings-in-linux/), а документацию в [`man v4l2-ctl`](https://www.mankier.com/1/v4l2-ctl).
-----
# Смотрите также
* [Запуск с помощью systemd-сервиса](https://github.com/pikvm/ustreamer/issues/16).
-----
# Лицензия
Copyright (C) 2018-2022 by Maxim Devaev mdevaev@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see https://www.gnu.org/licenses/.

54
janus/Makefile Normal file
View File

@@ -0,0 +1,54 @@
DESTDIR ?=
PREFIX ?= /usr/local
CC ?= gcc
CFLAGS ?= -O3
LDFLAGS ?=
# =====
_PLUGIN = libjanus_ustreamer.so
_CFLAGS = -fPIC -MD -c -std=c11 -Wall -Wextra -D_GNU_SOURCE $(shell pkg-config --cflags glib-2.0) $(CFLAGS)
_LDFLAGS = -shared -lm -pthread -lrt -ljansson $(shell pkg-config --libs glib-2.0) $(LDFLAGS)
_SRCS = $(shell ls src/*.c)
_BUILD = build
define optbool
$(filter $(shell echo $(1) | tr A-Z a-z), yes on 1)
endef
WITH_PTHREAD_NP ?= 1
ifneq ($(call optbool,$(WITH_PTHREAD_NP)),)
override _CFLAGS += -DWITH_PTHREAD_NP
endif
# =====
$(_PLUGIN): $(_SRCS:%.c=$(_BUILD)/%.o)
$(info == SO $@)
@ $(CC) $^ -o $@ $(_LDFLAGS)
$(_BUILD)/%.o: %.c
$(info -- CC $<)
@ mkdir -p $(dir $@) || true
@ $(CC) $< -o $@ $(_CFLAGS)
install: $(_PLUGIN)
mkdir -p $(DESTDIR)$(PREFIX)/lib/ustreamer/janus
install -m755 $(_PLUGIN) $(DESTDIR)$(PREFIX)/lib/ustreamer/janus/$(PLUGIN)
clean:
rm -rf $(_PLUGIN) $(_BUILD)
_OBJS = $(_SRCS:%.c=$(_BUILD)/%.o)
-include $(_OBJS:%.o=%.d)

1
janus/src/base64.c Symbolic link
View File

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

1
janus/src/base64.h Symbolic link
View File

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

1
janus/src/config.h Symbolic link
View File

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

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

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

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

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

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

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

1
janus/src/memsinksh.h Symbolic link
View File

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

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

@@ -0,0 +1,522 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include <stdint.h>
#include <stdbool.h>
#include <stdatomic.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <assert.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <linux/videodev2.h>
#include <pthread.h>
#include <jansson.h>
#include <janus/config.h>
#include <janus/plugins/plugin.h>
#include "config.h"
#include "tools.h"
#include "threading.h"
#include "list.h"
#include "memsinksh.h"
#include "rtp.h"
static int _plugin_init(janus_callbacks *gw, const char *config_file_path);
static void _plugin_destroy(void);
static void _plugin_create_session(janus_plugin_session *session, int *error);
static void _plugin_destroy_session(janus_plugin_session *session, int *error);
static json_t *_plugin_query_session(janus_plugin_session *session);
static void _plugin_setup_media(janus_plugin_session *session);
static void _plugin_hangup_media(janus_plugin_session *session);
static struct janus_plugin_result *_plugin_handle_message(
janus_plugin_session *session, char *transaction, json_t *msg, json_t *jsep);
static int _plugin_get_api_compatibility(void);
static int _plugin_get_version(void);
static const char *_plugin_get_version_string(void);
static const char *_plugin_get_description(void);
static const char *_plugin_get_name(void);
static const char *_plugin_get_author(void);
static const char *_plugin_get_package(void);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Woverride-init"
static janus_plugin _plugin = JANUS_PLUGIN_INIT(
.init = _plugin_init,
.destroy = _plugin_destroy,
.create_session = _plugin_create_session,
.destroy_session = _plugin_destroy_session,
.query_session = _plugin_query_session,
.setup_media = _plugin_setup_media,
.hangup_media = _plugin_hangup_media,
.handle_message = _plugin_handle_message,
.get_api_compatibility = _plugin_get_api_compatibility,
.get_version = _plugin_get_version,
.get_version_string = _plugin_get_version_string,
.get_description = _plugin_get_description,
.get_name = _plugin_get_name,
.get_author = _plugin_get_author,
.get_package = _plugin_get_package,
);
#pragma GCC diagnostic pop
janus_plugin *create(void) { // cppcheck-suppress unusedFunction
return &_plugin;
}
typedef struct _client_sx {
janus_plugin_session *session;
bool transmit;
LIST_STRUCT(struct _client_sx);
} _client_s;
static char *_g_memsink_obj = NULL;
const long double _g_wait_timeout = 1;
const long double _g_lock_timeout = 1;
const useconds_t _g_lock_polling = 1000;
const useconds_t _g_watchers_polling = 100000;
static _client_s *_g_clients = NULL;
static janus_callbacks *_g_gw = NULL;
static rtp_s *_g_rtp = NULL;
static pthread_t _g_tid;
static pthread_mutex_t _g_lock = PTHREAD_MUTEX_INITIALIZER;
static atomic_bool _g_ready = ATOMIC_VAR_INIT(false);
static atomic_bool _g_stop = ATOMIC_VAR_INIT(false);
static atomic_bool _g_has_watchers = ATOMIC_VAR_INIT(false);
#define JLOG_INFO(_msg, ...) JANUS_LOG(LOG_INFO, "== %s -- " _msg "\n", _plugin_get_name(), ##__VA_ARGS__)
#define JLOG_WARN(_msg, ...) JANUS_LOG(LOG_WARN, "== %s -- " _msg "\n", _plugin_get_name(), ##__VA_ARGS__)
#define JLOG_ERROR(_msg, ...) JANUS_LOG(LOG_ERR, "== %s -- " _msg "\n", _plugin_get_name(), ##__VA_ARGS__)
#define JLOG_PERROR(_msg, ...) { \
char _perror_buf[1024] = {0}; \
char *_perror_ptr = errno_to_string(errno, _perror_buf, 1023); \
JANUS_LOG(LOG_ERR, "[%s] " _msg ": %s\n", _plugin_get_name(), ##__VA_ARGS__, _perror_ptr); \
}
#define LOCK A_MUTEX_LOCK(&_g_lock)
#define UNLOCK A_MUTEX_UNLOCK(&_g_lock)
#define READY atomic_load(&_g_ready)
#define STOP atomic_load(&_g_stop)
#define HAS_WATCHERS atomic_load(&_g_has_watchers)
static void _relay_rtp_clients(const uint8_t *datagram, size_t size) {
janus_plugin_rtp packet = {
.video = true,
.buffer = (char *)datagram,
.length = size,
};
janus_plugin_rtp_extensions_reset(&packet.extensions);
LIST_ITERATE(_g_clients, client, {
if (client->transmit) {
_g_gw->relay_rtp(client->session, &packet);
}
});
}
static int _wait_frame(int fd, memsink_shared_s* mem, uint64_t last_id) {
long double deadline_ts = get_now_monotonic() + _g_wait_timeout;
long double now;
do {
int retval = flock_timedwait_monotonic(fd, _g_lock_timeout);
now = get_now_monotonic();
if (retval < 0 && errno != EWOULDBLOCK) {
JLOG_PERROR("Can't lock memsink");
return -1;
} else if (retval == 0) {
if (mem->magic == MEMSINK_MAGIC && mem->version == MEMSINK_VERSION && mem->id != last_id) {
return 0;
}
if (flock(fd, LOCK_UN) < 0) {
JLOG_PERROR("Can't unlock memsink");
return -1;
}
}
usleep(_g_lock_polling);
} while (now < deadline_ts);
return -2;
}
static int _get_frame(int fd, memsink_shared_s *mem, frame_s *frame, uint64_t *frame_id) {
frame_set_data(frame, mem->data, mem->used);
FRAME_COPY_META(mem, frame);
*frame_id = mem->id;
mem->last_client_ts = get_now_monotonic();
int retval = 0;
if (frame->format != V4L2_PIX_FMT_H264) {
JLOG_ERROR("Got non-H264 frame from memsink");
retval = -1;
}
if (flock(fd, LOCK_UN) < 0) {
JLOG_PERROR("Can't unlock memsink");
retval = -1;
}
return retval;
}
static void *_clients_thread(UNUSED void *arg) {
A_THREAD_RENAME("us_clients");
atomic_store(&_g_ready, true);
frame_s *frame = frame_init();
uint64_t frame_id = 0;
unsigned error_reported = 0;
# define IF_NOT_REPORTED(_error, ...) { \
if (error_reported != _error) { __VA_ARGS__; error_reported = _error; } \
}
while (!STOP) {
if (!HAS_WATCHERS) {
IF_NOT_REPORTED(1, {
JLOG_INFO("No active watchers, memsink disconnected");
});
usleep(_g_watchers_polling);
continue;
}
int fd = -1;
memsink_shared_s *mem = NULL;
if ((fd = shm_open(_g_memsink_obj, O_RDWR, 0)) <= 0) {
IF_NOT_REPORTED(2, {
JLOG_PERROR("Can't open memsink");
});
goto close_memsink;
}
if ((mem = memsink_shared_map(fd)) == NULL) {
IF_NOT_REPORTED(3, {
JLOG_PERROR("Can't map memsink");
});
goto close_memsink;
}
error_reported = 0;
JLOG_INFO("Memsink opened; reading frames ...");
while (!STOP && HAS_WATCHERS) {
int result = _wait_frame(fd, mem, frame_id);
if (result == 0) {
if (_get_frame(fd, mem, frame, &frame_id) != 0) {
goto close_memsink;
}
LOCK;
rtp_wrap_h264(_g_rtp, frame, _relay_rtp_clients);
UNLOCK;
} else if (result == -1) {
goto close_memsink;
}
}
close_memsink:
if (mem != NULL) {
JLOG_INFO("Memsink closed");
memsink_shared_unmap(mem);
mem = NULL;
}
if (fd > 0) {
close(fd);
fd = -1;
}
sleep(1); // error_delay
}
# undef IF_NOT_REPORTED
frame_destroy(frame);
return NULL;
}
static int _read_config(const char *config_dir_path) {
char *config_file_path;
janus_config *config = NULL;
A_ASPRINTF(config_file_path, "%s/%s.jcfg", config_dir_path, _plugin_get_package());
JLOG_INFO("Reading config file '%s' ...", config_file_path);
config = janus_config_parse(config_file_path);
if (config == NULL) {
JLOG_ERROR("Can't read config");
goto error;
}
janus_config_print(config);
janus_config_category *config_memsink = janus_config_get_create(config, NULL, janus_config_type_category, "memsink");
janus_config_item *config_memsink_obj = janus_config_get(config, config_memsink, janus_config_type_item, "object");
if (config_memsink_obj == NULL || config_memsink_obj->value == NULL || config_memsink_obj->value[0] == '\0') {
JLOG_ERROR("Missing config value: memsink.object");
goto error;
}
_g_memsink_obj = strdup(config_memsink_obj->value);
int retval = 0;
goto ok;
error:
retval = -1;
ok:
if (config) {
janus_config_destroy(config);
}
free(config_file_path);
return retval;
}
static int _plugin_init(janus_callbacks *gw, const char *config_dir_path) {
// https://groups.google.com/g/meetecho-janus/c/xoWIQfaoJm8
// sysctl -w net.core.rmem_default=500000
// sysctl -w net.core.wmem_default=500000
// sysctl -w net.core.rmem_max=1000000
// sysctl -w net.core.wmem_max=1000000
JLOG_INFO("Initializing plugin ...");
assert(!READY);
assert(!STOP);
if (gw == NULL || config_dir_path == NULL || _read_config(config_dir_path) < 0) {
return -1;
}
_g_gw = gw;
_g_rtp = rtp_init();
A_THREAD_CREATE(&_g_tid, _clients_thread, NULL);
return 0;
}
static void _plugin_destroy(void) {
JLOG_INFO("Destroying plugin ...");
atomic_store(&_g_stop, true);
if (READY) {
A_THREAD_JOIN(_g_tid);
}
LIST_ITERATE(_g_clients, client, {
LIST_REMOVE(_g_clients, client);
free(client);
});
_g_clients = NULL;
rtp_destroy(_g_rtp);
_g_rtp = NULL;
_g_gw = NULL;
if (_g_memsink_obj) {
free(_g_memsink_obj);
_g_memsink_obj = NULL;
}
}
#define IF_DISABLED(...) { if (!READY || STOP) { __VA_ARGS__ } }
static void _plugin_create_session(janus_plugin_session *session, int *error) {
IF_DISABLED({ *error = -1; return; });
LOCK;
JLOG_INFO("Creating session %p ...", session);
_client_s *client;
A_CALLOC(client, 1);
client->session = session;
client->transmit = true;
LIST_APPEND(_g_clients, client);
atomic_store(&_g_has_watchers, true);
UNLOCK;
}
static void _plugin_destroy_session(janus_plugin_session* session, int *error) {
IF_DISABLED({ *error = -1; return; });
LOCK;
bool found = false;
bool has_watchers = false;
LIST_ITERATE(_g_clients, client, {
if (client->session == session) {
JLOG_INFO("Removing session %p ...", session);
LIST_REMOVE(_g_clients, client);
free(client);
found = true;
} else {
has_watchers = (has_watchers || client->transmit);
}
});
if (!found) {
JLOG_WARN("No session %p", session);
*error = -2;
}
atomic_store(&_g_has_watchers, has_watchers);
UNLOCK;
}
static json_t *_plugin_query_session(janus_plugin_session *session) {
IF_DISABLED({ return NULL; });
json_t *info = NULL;
LOCK;
LIST_ITERATE(_g_clients, client, {
if (client->session == session) {
info = json_string("session_found");
break;
}
});
UNLOCK;
return info;
}
static void _set_transmit(janus_plugin_session *session, UNUSED const char *msg, bool transmit) {
IF_DISABLED({ return; });
LOCK;
bool found = false;
bool has_watchers = false;
LIST_ITERATE(_g_clients, client, {
if (client->session == session) {
client->transmit = transmit;
//JLOG_INFO("%s session %p", msg, session);
found = true;
}
has_watchers = (has_watchers || client->transmit);
});
if (!found) {
JLOG_WARN("No session %p", session);
}
atomic_store(&_g_has_watchers, has_watchers);
UNLOCK;
}
#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 { \
if (msg) json_decref(msg); \
if (jsep) json_decref(jsep); \
}
if (session == NULL || msg == NULL) {
free(transaction);
FREE_MSG_JSEP;
return janus_plugin_result_new(JANUS_PLUGIN_ERROR, (msg ? "No session" : "No message"), NULL);
}
# define PUSH_ERROR(_error, _reason) { \
/*JLOG_ERROR("Message error in session %p: %s", session, _reason);*/ \
json_t *_event = json_object(); \
json_object_set_new(_event, "ustreamer", json_string("event")); \
json_object_set_new(_event, "error_code", json_integer(_error)); \
json_object_set_new(_event, "error", json_string(_reason)); \
_g_gw->push_event(session, &_plugin, transaction, _event, NULL); \
json_decref(_event); \
}
json_t *request_obj = json_object_get(msg, "request");
if (request_obj == NULL) {
PUSH_ERROR(400, "Request missing");
goto ok_wait;
}
const char *request_str = json_string_value(request_obj);
if (!request_str) {
PUSH_ERROR(400, "Request not a string");
goto ok_wait;
}
//JLOG_INFO("Message: %s", request_str);
# define PUSH_STATUS(_status, _jsep) { \
json_t *_event = json_object(); \
json_object_set_new(_event, "ustreamer", json_string("event")); \
json_t *_result = json_object(); \
json_object_set_new(_result, "status", json_string(_status)); \
json_object_set_new(_event, "result", _result); \
_g_gw->push_event(session, &_plugin, transaction, _event, _jsep); \
json_decref(_event); \
}
if (!strcmp(request_str, "start")) {
PUSH_STATUS("started", NULL);
} else if (!strcmp(request_str, "stop")) {
PUSH_STATUS("stopped", NULL);
} else if (!strcmp(request_str, "watch")) {
char *sdp = rtp_make_sdp(_g_rtp);
if (sdp == NULL) {
PUSH_ERROR(503, "Haven't received SPS/PPS from memsink yet");
goto ok_wait;
}
//JLOG_INFO("SDP generated:\n%s", sdp);
json_t *offer_jsep = json_pack("{ssss}", "type", "offer", "sdp", sdp);
free(sdp);
PUSH_STATUS("started", offer_jsep);
json_decref(offer_jsep);
} else {
PUSH_ERROR(405, "Not implemented");
}
ok_wait:
FREE_MSG_JSEP;
return janus_plugin_result_new(JANUS_PLUGIN_OK_WAIT, NULL, NULL);
# undef PUSH_ERROR
# undef FREE_MSG_JSEP
}
static int _plugin_get_api_compatibility(void) { return JANUS_PLUGIN_API_VERSION; }
static int _plugin_get_version(void) { return VERSION_U; }
static const char *_plugin_get_version_string(void) { return VERSION; }
static const char *_plugin_get_description(void) { return "Pi-KVM uStreamer Janus plugin for H.264 video"; }
static const char *_plugin_get_name(void) { return "ustreamer"; }
static const char *_plugin_get_author(void) { return "Maxim Devaev <mdevaev@gmail.com>"; }
static const char *_plugin_get_package(void) { return "janus.plugin.ustreamer"; }
#undef STOP
#undef READY
#undef UNLOCK
#undef LOCK
#undef JLOG_PERROR
#undef JLOG_ERROR
#undef JLOG_WARN
#undef JLOG_INFO

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

@@ -0,0 +1,228 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# This source file is partially based on this code: #
# - https://github.com/catid/kvm/blob/master/kvm_pipeline/src #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "rtp.h"
#define PAYLOAD 96 // Payload type
#define PRE 3 // Annex B prefix length
void _rtp_process_nalu(rtp_s *rtp, const uint8_t *data, size_t size, uint32_t pts, bool marked, rtp_callback_f callback);
static void _rtp_write_header(rtp_s *rtp, uint32_t pts, bool marked);
static ssize_t _find_annexb(const uint8_t *data, size_t size);
rtp_s *rtp_init(void) {
rtp_s *rtp;
A_CALLOC(rtp, 1);
rtp->ssrc = triple_u32(get_now_monotonic_u64());
rtp->sps = frame_init();
rtp->pps = frame_init();
A_MUTEX_INIT(&rtp->mutex);
return rtp;
}
void rtp_destroy(rtp_s *rtp) {
A_MUTEX_DESTROY(&rtp->mutex);
frame_destroy(rtp->pps);
frame_destroy(rtp->sps);
free(rtp);
}
char *rtp_make_sdp(rtp_s *rtp) {
A_MUTEX_LOCK(&rtp->mutex);
if (rtp->sps->used == 0 || rtp->pps->used == 0) {
A_MUTEX_UNLOCK(&rtp->mutex);
return NULL;
}
char *sps = NULL;
char *pps = NULL;
base64_encode(rtp->sps->data, rtp->sps->used, &sps, NULL);
base64_encode(rtp->pps->data, rtp->pps->used, &pps, NULL);
A_MUTEX_UNLOCK(&rtp->mutex);
// https://tools.ietf.org/html/rfc6184
// https://github.com/meetecho/janus-gateway/issues/2443
char *sdp;
A_ASPRINTF(sdp,
"v=0" RN
"o=- %" PRIu64 " 1 IN IP4 127.0.0.1" RN
"s=Pi-KVM uStreamer" RN
"t=0 0" RN
"m=video 1 RTP/SAVPF %d" RN
"c=IN IP4 0.0.0.0" RN
"a=rtpmap:%d H264/90000" RN
"a=fmtp:%d profile-level-id=42E01F" RN
"a=fmtp:%d packetization-mode=1" RN
"a=fmtp:%d sprop-sps=%s" RN
"a=fmtp:%d sprop-pps=%s" RN
"a=rtcp-fb:%d nack" RN
"a=rtcp-fb:%d nack pli" RN
"a=rtcp-fb:%d goog-remb" RN
"a=sendonly" RN,
get_now_id() >> 1, PAYLOAD, PAYLOAD, PAYLOAD, PAYLOAD,
PAYLOAD, sps,
PAYLOAD, pps,
PAYLOAD, PAYLOAD, PAYLOAD
);
free(sps);
free(pps);
return sdp;
}
void rtp_wrap_h264(rtp_s *rtp, const frame_s *frame, rtp_callback_f callback) {
// There is a complicated logic here but everything works as it should:
// - https://github.com/pikvm/ustreamer/issues/115#issuecomment-893071775
assert(frame->format == V4L2_PIX_FMT_H264);
const uint32_t pts = get_now_monotonic_u64() * 9 / 100; // PTS units are in 90 kHz
ssize_t last_offset = -PRE;
while (true) { // Find and iterate by nalus
const size_t next_start = last_offset + PRE;
ssize_t offset = _find_annexb(frame->data + next_start, frame->used - next_start);
if (offset < 0) {
break;
}
offset += next_start;
if (last_offset >= 0) {
const uint8_t *data = frame->data + last_offset + PRE;
size_t size = offset - last_offset - PRE;
if (data[size - 1] == 0) { // Check for extra 00
--size;
}
_rtp_process_nalu(rtp, data, size, pts, false, callback);
}
last_offset = offset;
}
if (last_offset >= 0) {
const uint8_t *data = frame->data + last_offset + PRE;
size_t size = frame->used - last_offset - PRE;
_rtp_process_nalu(rtp, data, size, pts, true, callback);
}
}
void _rtp_process_nalu(rtp_s *rtp, const uint8_t *data, size_t size, uint32_t pts, bool marked, rtp_callback_f callback) {
const unsigned ref_idc = (data[0] >> 5) & 3;
const unsigned type = data[0] & 0x1F;
frame_s *ps = NULL;
switch (type) {
case 7: ps = rtp->sps; break;
case 8: ps = rtp->pps; break;
}
if (ps) {
A_MUTEX_LOCK(&rtp->mutex);
frame_set_data(ps, data, size);
A_MUTEX_UNLOCK(&rtp->mutex);
}
# define HEADER_SIZE 12
if (size + HEADER_SIZE <= RTP_DATAGRAM_SIZE) {
_rtp_write_header(rtp, pts, marked);
memcpy(rtp->datagram + HEADER_SIZE, data, size);
callback(rtp->datagram, size + HEADER_SIZE);
return;
}
const size_t fu_overhead = HEADER_SIZE + 2; // FU-A overhead
const uint8_t *src = data + 1;
ssize_t remaining = size - 1;
bool first = true;
while (remaining > 0) {
ssize_t frag_size = RTP_DATAGRAM_SIZE - fu_overhead;
const bool last = (remaining <= frag_size);
if (last) {
frag_size = remaining;
}
_rtp_write_header(rtp, pts, (marked && last));
rtp->datagram[HEADER_SIZE] = 28 | (ref_idc << 5);
uint8_t fu = type;
if (first) {
fu |= 0x80;
}
if (last) {
fu |= 0x40;
}
rtp->datagram[HEADER_SIZE + 1] = fu;
memcpy(rtp->datagram + fu_overhead, src, frag_size);
callback(rtp->datagram, fu_overhead + frag_size);
src += frag_size;
remaining -= frag_size;
first = false;
}
# undef HEADER_SIZE
}
static void _rtp_write_header(rtp_s *rtp, uint32_t pts, bool marked) {
uint32_t word0 = 0x80000000;
if (marked) {
word0 |= 1 << 23;
}
word0 |= (PAYLOAD & 0x7F) << 16;
word0 |= rtp->seq;
++rtp->seq;
# define WRITE_BE_U32(_offset, _value) *((uint32_t *)(rtp->datagram + _offset)) = __builtin_bswap32(_value)
WRITE_BE_U32(0, word0);
WRITE_BE_U32(4, pts);
WRITE_BE_U32(8, rtp->ssrc);
# undef WRITE_BE_U32
}
static ssize_t _find_annexb(const uint8_t *data, size_t size) {
// Parses buffer for 00 00 01 start codes
if (size >= PRE) {
for (size_t index = 0; index <= size - PRE; ++index) {
if (data[index] == 0 && data[index + 1] == 0 && data[index + 2] == 1) {
return index;
}
}
}
return -1;
}
#undef PRE
#undef PAYLOAD

View File

@@ -1,7 +1,11 @@
/*****************************************************************************
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# This source file is partially based on this code: #
# - https://github.com/catid/kvm/blob/master/kvm_pipeline/src #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -19,60 +23,47 @@
*****************************************************************************/
#include <stdbool.h>
#pragma once
#include <event2/event.h>
#include <event2/http.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <inttypes.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>
#include <linux/videodev2.h>
#include <pthread.h>
#include "tools.h"
#include "stream.h"
#include "threading.h"
#include "frame.h"
#include "base64.h"
struct stream_client_t {
struct http_server_t *server;
struct evhttp_request *request;
bool need_initial;
bool need_first_frame;
struct stream_client_t *prev;
struct stream_client_t *next;
};
struct exposed_t {
struct picture_t picture;
unsigned width;
unsigned height;
unsigned fps;
bool online;
unsigned dropped;
};
struct http_server_runtime_t {
struct event_base *base;
struct evhttp *http;
struct event *refresh;
struct stream_t *stream;
struct exposed_t *exposed;
struct stream_client_t *stream_clients;
unsigned stream_clients_count;
unsigned drop_same_frames_blank;
};
struct http_server_t {
char *host;
unsigned port;
unsigned drop_same_frames;
unsigned fake_width;
unsigned fake_height;
unsigned timeout;
struct http_server_runtime_t *run;
};
// https://stackoverflow.com/questions/47635545/why-webrtc-chose-rtp-max-packet-size-to-1200-bytes
#define RTP_DATAGRAM_SIZE 1200
struct http_server_t *http_server_init(struct stream_t *stream);
void http_server_destroy(struct http_server_t *server);
typedef struct {
uint32_t ssrc;
uint16_t seq;
int http_server_listen(struct http_server_t *server);
void http_server_loop(struct http_server_t *server);
void http_server_loop_break(struct http_server_t *server);
uint8_t datagram[RTP_DATAGRAM_SIZE];
frame_s *sps; // Actually not a frame, just a bytes storage
frame_s *pps;
pthread_mutex_t mutex;
} rtp_s;
typedef void (*rtp_callback_f)(const uint8_t *datagram, size_t size);
rtp_s *rtp_init(void);
void rtp_destroy(rtp_s *rtp);
char *rtp_make_sdp(rtp_s *rtp);
void rtp_wrap_h264(rtp_s *rtp, const frame_s *frame, rtp_callback_f callback);

1
janus/src/threading.h Symbolic link
View File

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

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

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

2
linters/.dockerignore Normal file
View File

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

26
linters/Dockerfile Normal file
View File

@@ -0,0 +1,26 @@
FROM archlinux/archlinux:base-devel
RUN mkdir -p /etc/pacman.d/hooks \
&& ln -s /dev/null /etc/pacman.d/hooks/30-systemd-tmpfiles.hook
RUN echo "Server = http://mirror.yandex.ru/archlinux/\$repo/os/\$arch" > /etc/pacman.d/mirrorlist
RUN pacman -Syu --noconfirm \
&& pacman -S --needed --noconfirm \
vim \
git \
libjpeg \
libevent \
libutil-linux \
libbsd \
python \
python-pip \
python-tox \
cppcheck \
npm \
&& (pacman -Sc --noconfirm || true) \
&& rm -rf /var/cache/pacman/pkg/*
RUN npm install htmlhint -g
CMD /bin/bash

3
linters/cppcheck.h Normal file
View File

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

9
linters/flake8.ini Normal file
View File

@@ -0,0 +1,9 @@
[flake8]
inline-quotes = double
max-line-length = 160
ignore = W503, E227, E241, E252, Q003
# W503 line break before binary operator
# E227 missing whitespace around bitwise or shift operator
# E241 multiple spaces after
# E252 missing whitespace around parameter equals
# Q003 Change outer quotes to avoid escaping inner quotes

5
linters/mypy.ini Normal file
View File

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

64
linters/pylint.ini Normal file
View File

@@ -0,0 +1,64 @@
[MASTER]
ignore = .git
[DESIGN]
min-public-methods = 0
max-args = 10
[TYPECHECK]
ignored-classes=
AioQueue,
[MESSAGES CONTROL]
disable =
file-ignored,
locally-disabled,
fixme,
missing-docstring,
no-init,
no-self-use,
superfluous-parens,
abstract-class-not-used,
abstract-class-little-used,
duplicate-code,
bad-continuation,
bad-whitespace,
star-args,
broad-except,
redundant-keyword-arg,
wrong-import-order,
too-many-ancestors,
no-else-return,
len-as-condition,
unspecified-encoding,
[REPORTS]
msg-template = {symbol} -- {path}:{line}({obj}): {msg}
[FORMAT]
max-line-length = 160
[BASIC]
# List of builtins function names that should not be used, separated by a comma
bad-functions =
# Good variable names which should always be accepted, separated by a comma
good-names = _, __, x, y, ws, make-html-h, make-jpeg-h
# Regular expression matching correct method names
method-rgx = [a-z_][a-z0-9_]{2,50}$
# Regular expression matching correct function names
function-rgx = [a-z_][a-z0-9_]{2,50}$
# Regular expression which should only match correct module level names
const-rgx = ([a-zA-Z_][a-zA-Z0-9_]*)$
# Regular expression which should only match correct argument names
argument-rgx = [a-z_][a-z0-9_]{1,30}$
# Regular expression which should only match correct variable names
variable-rgx = [a-z_][a-z0-9_]{1,30}$
# Regular expression which should only match correct instance attribute names
attr-rgx = [a-z_][a-z0-9_]{1,30}$

51
linters/tox.ini Normal file
View File

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

91
man/ustreamer-dump.1 Normal file
View File

@@ -0,0 +1,91 @@
.\" 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.1" "January 2021"
.SH NAME
ustreamer-dump \- Dump uStreamer's memory sink to file
.SH SYNOPSIS
.B ustreamer-dump
.RI [OPTIONS]
.SH DESCRIPTION
µStreamer-dump (\fBustreamer-dump\fP) writes a local stream from ustreamer to a file or redirect it to other utilities (such as \fBffmpeg\fR).
.SH USAGE
\fBustreamer\fR requires at least the \fB\-\-sink\fR option to operate.
To output ustreamers sink "test" to ffmpeg, and into a file called test.mp4:
\fBustreamer-dump \e\fR
.RS
\fB\-\-sink=test \e\fR # Use ustreamer sink "test"
.nf
\fB\-\-output\ \- \e\fR # Output to stdout
\fB|\ ffmpeg\ \-use_wallclock_as_timestamps\ 1\ \-i\ pipe:\ \-c:v\ libx264\ test\.mp4\fR
.SH OPTIONS
.SS "Sink options"
.TP
.BR \-s ", " \-\-sink\ \fIname
Memory sink ID. No default.
.TP
.BR \-t ", " \-\-sink\-timeout\ \fIsec
Timeout for the upcoming frame. Default: 1.
.TP
.BR \-o ", " \-\-output\ \fIfilename
Filename to dump output to. Use '-' for stdout. Default: just consume the sink.
.TP
.BR \-j ", " \-\-output-json
Format output as JSON. Required option --output. Default: disabled.
.TP
.BR \-c ", " \-\-count\ \fIN
Limit the number of frames. Default: 0 (infinite).
.TP
.BR \-i ", "\-\-interval\ \fIsec
Delay between reading frames (float). Default: 0.
.SS "Logging options"
.TP
.BR \-\-log\-level\ \fIN
Verbosity level of messages from 0 (info) to 3 (debug). Enabling debugging messages can slow down the program.
Available levels: 0 (info), 1 (performance), 2 (verbose), 3 (debug).
Default: 0.
.TP
.BR \-\-perf
Enable performance messages (same as \-\-log\-level=1). Default: disabled.
.TP
.BR \-\-verbose
Enable verbose messages and lower (same as \-\-log\-level=2). Default: disabled.
.TP
.BR \-\-debug
Enable debug messages and lower (same as \-\-log\-level=3). Default: disabled.
.TP
.BR \-\-force\-log\-colors
Force color logging. Default: colored if stderr is a TTY.
.TP
.BR \-\-no\-log\-colors
Disable color logging. Default: ditto.
.SS "Help options"
.TP
.BR \-h ", " \-\-help
Print this text and exit.
.TP
.BR \-v ", " \-\-version
Print version and exit.
.SH "SEE ALSO"
.BR ustreamer (1)
.SH BUGS
Please file any bugs and issues at \fIhttps://github.com/pikvm/ustreamer/issues\fR
.SH AUTHOR
Maxim Devaev <mdevaev@gmail.com>
.SH HOMEPAGE
\fIhttps://pikvm.org/\fR
.SH COPYRIGHT
GNU General Public License v3.0

333
man/ustreamer.1 Normal file
View File

@@ -0,0 +1,333 @@
.\" 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.1" "November 2020"
.SH NAME
ustreamer \- stream MJPEG video from any V4L2 device to the network
.SH SYNOPSIS
.B ustreamer
.RI [OPTIONS]
.SH DESCRIPTION
µStreamer (\fBustreamer\fP) is a lightweight and very quick server to stream MJPEG video from any V4L2 device to the network. All new browsers have native support of this video format, as well as most video players such as mplayer, VLC etc. µStreamer is a part of the Pi-KVM project designed to stream VGA and HDMI screencast hardware data with the highest resolution and FPS possible.
.SH USAGE
Without arguments, \fBustreamer\fR will try to open \fB/dev/video0\fR with 640x480 resolution and start streaming on \fBhttp://127\.0\.0\.1:8080\fR\. You can override this behavior using parameters \fB\-\-device\fR, \fB\-\-host\fR and \fB\-\-port\fR\. For example, to stream to the world, run: \fBustreamer --device=/dev/video1 --host=0.0.0.0 --port=80\fR
Please note that since µStreamer v2\.0 cross\-domain requests were disabled by default for security reasons\. To enable the old behavior, use the option \fB\-\-allow\-origin=\e*\fR\.
For example, the recommended way of running µStreamer with Auvidea B101 on a Raspberry Pi is:
\fBustreamer \e\fR
.RS
\fB\-\-format=uyvy \e\fR # Device input format
.nf
\fB\-\-encoder=m2m-image \e\fR # Hardware encoding with V4L2 M2M intraface
.nf
\fB\-\-workers=3 \e\fR # Maximum workers for V4L2 encoder
.nf
\fB\-\-persistent \e\fR # Don\'t re\-initialize device on timeout (for example when HDMI cable was disconnected)
.nf
\fB\-\-dv\-timings \e\fR # Use DV\-timings
.nf
\fB\-\-drop\-same\-frames=30\fR # Save the traffic\fR
.RE
.P
Please note that to use \fB\-\-drop\-same\-frames\fR for different browsers you need to use some specific URL \fB/stream\fR parameters (see URL \fB/\fR for details)\.
.P
You can always view the full list of options with \fBustreamer \-\-help\fR\. Some features may not be available on your platform. To find out which features are enabled, use \fBustreamer \-\-features\fR.
.SH OPTIONS
.SS "Capturing options"
.TP
.BR \-d\ \fI/dev/path ", " \-\-device\ \fI/dev/path
Path to V4L2 device. Default: /dev/video0.
.TP
.BR \-i\ \fIN ", " \-\-input\ \fIN
Input channel. Default: 0.
.TP
.BR \-r\ \fIWxH ", " \-\-resolution\ \fIWxH
Initial image resolution. Default: 640x480.
.TP
.BR \-m\ \fIfmt ", " \-\-format\ \fIfmt
Image format.
Available: YUYV, UYVY, RGB565, RGB24, JPEG; default: YUYV.
.TP
.BR \-a\ \fIstd ", " \-\-tv\-standard\ \fIstd
Force TV standard.
Available: PAL, NTSC, SECAM; default: disabled.
.TP
.BR \-I\ \fImethod ", " \-\-io\-method\ \fImethod
Set V4L2 IO method (see kernel documentation). Changing of this parameter may increase the performance. Or not.
Available: MMAP, USERPTR; default: MMAP.
.TP
.BR \-f\ \fIN ", " \-\-desired\-fps\ \fIN
Desired FPS. Default: maximum possible.
.TP
.BR \-z\ \fIN ", " \-\-min\-frame\-size\ \fIN
Drop frames smaller then this limit. Useful if the device produces small\-sized garbage frames. Default: 128 bytes.
.TP
.BR \-n ", " \-\-persistent
Don't re\-initialize device on timeout. Default: disabled.
.TP
.BR \-t ", " \-\-dv\-timings
Enable DV-timings querying and events processing to automatic resolution change. Default: disabled.
.TP
.BR \-b\ \fIN ", " \-\-buffers\ \fIN
The number of buffers to receive data from the device. Each buffer may processed using an independent thread.
Default: 2 (the number of CPU cores (but not more than 4) + 1).
.TP
.BR \-w\ \fIN ", " \-\-workers\ \fIN
The number of worker threads but not more than buffers.
Default: 1 (the number of CPU cores (but not more than 4)).
.TP
.BR \-q\ \fIN ", " \-\-quality\ \fIN
Set quality of JPEG encoding from 1 to 100 (best). Default: 80.
Note: If HW encoding is used (JPEG source format selected), this parameter attempts to configure the camera or capture device hardware's internal encoder. It does not re\-encode MJPEG to MJPEG to change the quality level for sources that already output MJPEG.
.TP
.BR \-c\ \fItype ", " \-\-encoder\ \fItype
Use specified encoder. It may affect the number of workers.
CPU ─ Software MJPEG encoding (default).
HW ─ Use pre-encoded MJPEG frames directly from camera hardware.
M2M-VIDEO ─ GPU-accelerated MJPEG encoding.
M2M-IMAGE ─ GPU-accelerated JPEG encoding.
NOOP ─ Don't compress MJPEG stream (do nothing).
.TP
.BR \-g\ \fIWxH,... ", " \-\-glitched\-resolutions\ \fIWxH,...
It doesn't do anything. Still here for compatibility.
.TP
.BR \-k\ \fIpath ", " \-\-blank\ \fIpath
Path to JPEG file that will be shown when the device is disconnected during the streaming. Default: black screen 640x480 with 'NO SIGNAL'.
.TP
.BR \-K\ \fIsec ", " \-\-last\-as\-blank\ \fIsec
Show the last frame received from the camera after it was disconnected, but no more than specified time (or endlessly if 0 is specified). If the device has not yet been online, display 'NO SIGNAL' or the image specified by option \-\-blank. Note: currently this option has no effect on memory sinks. Default: disabled.
.TP
.BR \-l ", " \-\-slowdown
Slowdown capturing to 1 FPS or less when no stream or sink clients are connected. Useful to reduce CPU consumption. Default: disabled.
.TP
.BR \-\-device\-timeout\ \fIsec
Timeout for device querying. Default: 1.
.TP
.BR \-\-device\-error\-delay\ \fIsec
Delay before trying to connect to the device again after an error (timeout for example). Default: 1.
.TP
.BR \-\-m2m\-device\ \fI/dev/path
Path to V4L2 mem-to-mem encoder device. Default: auto-select.
.SS "Image control options"
.TP
.BR \-\-image\-default
Reset all image settings below to default. Default: no change.
.TP
.BR \-\-brightness\ \fIN ", " \fIauto ", " \fIdefault
Set brightness. Default: no change.
.TP
.BR \-\-contrast\ \fIN ", " \fIdefault
Set contrast. Default: no change.
.TP
.BR \-\-saturation\ \fIN ", " \fIdefault
Set saturation. Default: no change.
.TP
.BR \-\-hue\ \fIN ", " \fIauto ", " \fIdefault
Set hue. Default: no change.
.TP
.BR \-\-gamma\ \fIN ", " \fIdefault
Set gamma. Default: no change.
.TP
.BR \-\-sharpness\ \fIN ", " \fIdefault
Set sharpness. Default: no change.
.TP
.BR \-\-backlight\-compensation\ \fIN ", " \fIdefault
Set backlight compensation. Default: no change.
.TP
.BR \-\-white\-balance\ \fIN ", " \fIauto ", " \fIdefault
Set white balance. Default: no change.
.TP
.BR \-\-gain\ \fIN ", " \fIauto ", " \fIdefault
Set gain. Default: no change.
.TP
.BR \-\-color\-effect\ \fIN ", " \fIdefault
Set color effect. Default: no change.
.TP
.BR \-\-flip\-vertical\ \fI1 ", " \fI0 ", " \fIdefault
Set vertical flip. Default: no change.
.TP
.BR \-\-flip\-horizontal\ \fI1 ", " \fI0 ", " \fIdefault
Set horizontal flip. Default: no change.
.SS "HTTP server options"
.TP
.BR \-s\ \fIaddress ", " \-\-host\ \fIaddress
Listen on Hostname or IP. Default: 127.0.0.1.
.TP
.BR \-p\ \fIN ", " \-\-port\ \fIN
Bind to this TCP port. Default: 8080.
.TP
.BR \-U\ \fIpath ", " \-\-unix\ \fIpath
Bind to UNIX domain socket. Default: disabled.
.TP
.BR \-D ", " \-\-unix\-rm
Try to remove old unix socket file before binding. default: disabled.
.TP
.BR \-M\ \fImode ", " \-\-unix\-mode\ \fImode
Set UNIX socket file permissions (like 777). Default: disabled.
.TP
.BR \-S ", " \-\-systemd
Bind to systemd socket for socket activation. Required \fBWITH_SYSTEMD\fR feature. Default: disabled.
.TP
.BR \-\-user\ \fIname
HTTP basic auth user. Default: disabled.
.TP
.BR \-\-passwd\ \fIstr
HTTP basic auth passwd. Default: empty.
.TP
.BR \-\-static\ \fIpath
Path to dir with static files instead of embedded root index page. Symlinks are not supported for security reasons. Default: disabled.
.TP
.BR \-e\ \fIN ", " \-\-drop\-same\-frames\ \fIN
Don't send identical frames to clients, but no more than specified number. It can significantly reduce the outgoing traffic, but will increase the CPU loading. Don't use this option with analog signal sources or webcams, it's useless. Default: disabled.
.TP
.BR \-R\ \fIWxH ", " \-\-fake\-resolution\ \fIWxH
Override image resolution for the /state. Default: disabled.
.TP
.BR \-\-tcp\-nodelay
Set TCP_NODELAY flag to the client /stream socket. Only for TCP socket.
Default: disabled.
.TP
.BR \-\-allow\-origin\ \fIstr
Set Access\-Control\-Allow\-Origin header. Default: disabled.
.TP
.BR \-\-server\-timeout\ \fIsec
Timeout for client connections. Default: 10.
.SS "JPEG sink options"
With shared memory sink you can write a stream to a file. See \fBustreamer-dump\fR(1) for more info.
.TP
.BR \-\-sink\ \fIname
Use the specified shared memory object to sink JPEG frames. Default: disabled.
.TP
.BR \-\-sink\-mode\ \fImode
Set JPEG sink permissions (like 777). Default: 660.
.TP
.BR \-\-sink\-rm
Remove shared memory on stop. Default: disabled.
.TP
.BR \-\-sink\-client\-ttl\ \fIsec
Client TTL. Default: 10.
.TP
.BR \-\-sink\-timeout\ \fIsec
Timeout for lock. Default: 1.
.SS "H264 sink options"
.TP
.BR \-\-h264\-sink\ \fIname
Use the specified shared memory object to sink H264 frames. Default: disabled.
.TP
.BR \-\-h264\-sink\-mode\ \fImode
Set H264 sink permissions (like 777). Default: 660.
.TP
.BR \-\-h264\-sink\-rm
Remove shared memory on stop. Default: disabled.
.TP
.BR \-\-h264\-sink\-client\-ttl\ \fIsec
Client TTL. Default: 10.
.TP
.BR \-\-h264\-sink\-timeout\ \fIsec
Timeout for lock. Default: 1.
.TP
.BR \-\-h264\-bitrate\ \fIkbps
H264 bitrate in Kbps. Default: 5000.
.TP
.BR \-\-h264\-gop\ \fIN
Intarval between keyframes. Default: 30.
.TP
.BR \-\-h264\-m2m\-device\ \fI/dev/path
Path to V4L2 mem-to-mem encoder device. Default: auto-select.
.SS "Process options"
.TP
.BR \-\-exit\-on\-parent\-death
Exit the program if the parent process is dead. Required \fBHAS_PDEATHSIG\fR feature. Default: disabled.
.TP
.BR \-\-exit\-on\-no\-clients \fIsec
Exit the program if there have been no stream or sink clients or any HTTP requests in the last N seconds. Default: 0 (disabled).
.TP
.BR \-\-process\-name\-prefix\ \fIstr
Set process name prefix which will be displayed in the process list like '\fIstr: ustreamer \-\-blah\-blah\-blah'\fR. Required \fBWITH_SETPROCTITLE\fR feature. Default: disabled.
.TP
.BR \-\-notify\-parent
Send SIGUSR2 to the parent process when the stream parameters are changed. Checking changes is performed for the online flag and image resolution. Required \fBWITH_SETPROCTITLE\fR feature.
.SS "GPIO options"
Available only if \fBWITH_GPIO\fR feature enabled.
.TP
.BR \-\-gpio\-device\ \fI/dev/path
Path to GPIO character device. Default: /dev/gpiochip0.
.TP
.BR \-\-gpio\-consumer\-prefix\ \fIstr
Consumer prefix for GPIO outputs. Default: ustreamer.
.TP
.BR \-\-gpio\-prog\-running\ \fIpin
Set 1 on GPIO pin while µStreamer is running. Default: disabled.
.TP
.BR \-\-gpio\-stream\-online\ \fIpin
Set 1 while streaming. Default: disabled.
.TP
.BR \-\-gpio\-has\-http\-clients\ \fIpin
Set 1 while stream has at least one client. Default: disabled.
.SS "Logging options"
.TP
.BR \-\-log\-level\ \fIN
Verbosity level of messages from 0 (info) to 3 (debug). Enabling debugging messages can slow down the program.
Available levels: 0 (info), 1 (performance), 2 (verbose), 3 (debug).
Default: 0.
.TP
.BR \-\-perf
Enable performance messages (same as \-\-log\-level=1). Default: disabled.
.TP
.BR \-\-verbose
Enable verbose messages and lower (same as \-\-log\-level=2). Default: disabled.
.TP
.BR \-\-debug
Enable debug messages and lower (same as \-\-log\-level=3). Default: disabled.
.TP
.BR \-\-force\-log\-colors
Force color logging. Default: colored if stderr is a TTY.
.TP
.BR \-\-no\-log\-colors
Disable color logging. Default: ditto.
.SS "Help options"
.TP
.BR \-h ", " \-\-help
Print this text and exit.
.TP
.BR \-v ", " \-\-version
Print version and exit.
.TP
.BR \-\-features
Print list of supported features.
.SH "SEE ALSO"
.BR ustreamer-dump (1)
.SH BUGS
Please file any bugs and issues at \fIhttps://github.com/pikvm/ustreamer/issues\fR
.SH AUTHOR
Maxim Devaev <mdevaev@gmail.com>
.SH HOMEPAGE
\fIhttps://pikvm.org/\fR
.SH COPYRIGHT
GNU General Public License v3.0

53
pkg/arch/PKGBUILD Normal file
View File

@@ -0,0 +1,53 @@
# Contributor: Maxim Devaev <mdevaev@gmail.com>
# Author: Maxim Devaev <mdevaev@gmail.com>
pkgname=ustreamer
pkgver=5.1
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)
makedepends=(gcc make)
source=(${pkgname}::"git+https://github.com/pikvm/ustreamer#commit=v${pkgver}")
md5sums=(SKIP)
_options="WITH_GPIO=1"
if [ -e /usr/bin/python3 ]; then
_options="$_options WITH_PYTHON=1"
depends+=(python)
makedepends+=(python-setuptools)
fi
if [ -e /usr/include/janus/plugins/plugin.h ];then
depends+=(janus-gateway-pikvm)
makedepends+=(janus-gateway-pikvm)
_options="$_options WITH_JANUS=1"
fi
if [ -e /usr/include/systemd/sd-daemon.h ];then
depends+=(systemd)
makedepends+=(systemd)
_options="$_options WITH_SYSTEMD=1"
fi
# LD does not link mmal with this option
# This DOESN'T affect setup.py
LDFLAGS="${LDFLAGS//--as-needed/}"
export LDFLAGS="${LDFLAGS//,,/,}"
build() {
cd "$srcdir"
rm -rf $pkgname-build
cp -r $pkgname $pkgname-build
cd $pkgname-build
make $_options CFLAGS="$CFLAGS" LDFLAGS="$LDFLAGS" $MAKEFLAGS
}
package() {
cd "$srcdir/$pkgname-build"
make $_options CFLAGS="$CFLAGS" LDFLAGS="$LDFLAGS" DESTDIR="$pkgdir" PREFIX=/usr install
}

View File

@@ -0,0 +1,39 @@
FROM balenalib/raspberrypi3-debian:build as build
RUN ["cross-build-start"]
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
gcc \
libjpeg8-dev \
libbsd-dev \
libgpiod-dev \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /build/ustreamer/
COPY . .
RUN make -j5 WITH_GPIO=1
RUN ["cross-build-end"]
FROM balenalib/raspberrypi3-debian:run as RUN
RUN ["cross-build-start"]
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
libevent-2.1 \
libevent-pthreads-2.1-6 \
libjpeg8 \
libbsd0 \
libgpiod2 \
&& rm -rf /var/lib/apt/lists/*
RUN ["cross-build-end"]
WORKDIR /ustreamer
COPY --from=build /build/ustreamer/ustreamer .
EXPOSE 8080
ENTRYPOINT ["./ustreamer", "--host=::"]
# vim: syntax=dockerfile

View File

@@ -0,0 +1,32 @@
FROM balenalib/raspberrypi3-debian:build as build
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
gcc \
libjpeg8-dev \
libbsd-dev \
libgpiod-dev \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /build/ustreamer/
COPY . .
RUN make -j5 WITH_GPIO=1
FROM balenalib/raspberrypi3-debian:run as RUN
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
libevent-2.1 \
libevent-pthreads-2.1-6 \
libjpeg8 \
libbsd0 \
libgpiod2 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /ustreamer
COPY --from=build /build/ustreamer/ustreamer .
EXPOSE 8080
ENTRYPOINT ["./ustreamer", "--host=::"]
# vim: syntax=dockerfile

View File

@@ -0,0 +1,38 @@
FROM debian:buster-slim as build
RUN apt-get update \
&& apt-get install -y \
ca-certificates \
make \
gcc \
git \
libevent-dev \
libjpeg62-turbo-dev \
libbsd-dev \
libgpiod-dev \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /build/ustreamer/
COPY . .
RUN make -j5 WITH_GPIO=1
FROM debian:buster-slim as run
RUN apt-get update \
&& apt-get install -y \
ca-certificates \
libevent-2.1 \
libevent-pthreads-2.1-6 \
libjpeg62-turbo \
libbsd0 \
libgpiod2 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /ustreamer
COPY --from=build /build/ustreamer/ustreamer .
#ENV LD_LIBRARY_PATH=/opt/vc/lib
EXPOSE 8080
ENTRYPOINT ["./ustreamer", "--host=0.0.0.0"]
# vim: syntax=dockerfile

View File

@@ -0,0 +1,30 @@
# Copyright 2019 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2
EAPI=7
inherit git-r3
DESCRIPTION="uStreamer - Lightweight and fast MJPEG-HTTP streamer"
HOMEPAGE="https://github.com/pikvm/ustreamer"
EGIT_REPO_URI="https://github.com/pikvm/ustreamer.git"
LICENSE="GPL-3"
SLOT="0"
KEYWORDS="~amd64"
IUSE=""
DEPEND="
>=dev-libs/libevent-2.1.8
>=media-libs/libjpeg-turbo-1.5.3
>=dev-libs/libbsd-0.9.1
"
RDEPEND="${DEPEND}"
BDEPEND=""
src_install() {
dobin ustreamer
dobin ustreamer-dump
doman man/ustreamer.1
doman man/ustreamer-dump.1
}

46
pkg/openwrt/Makefile Normal file
View File

@@ -0,0 +1,46 @@
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#
include $(TOPDIR)/rules.mk
PKG_NAME:=ustreamer
PKG_VERSION:=5.1
PKG_RELEASE:=1
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://github.com/pikvm/ustreamer.git
PKG_SOURCE_VERSION:=v$(PKG_VERSION)
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.xz
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
PKG_LICENSE:=GPL-3.0
PKG_LICENSE_FILES:=LICENSE
include $(INCLUDE_DIR)/package.mk
define Package/ustreamer
SECTION:=multimedia
CATEGORY:=Multimedia
TITLE:=uStreamer
DEPENDS:=+libpthread +libjpeg +libv4l +libbsd +libevent2 +libevent2-core +libevent2-extra +libevent2-pthreads
URL:=https://github.com/pikvm/ustreamer
endef
define Package/ustreamer/description
µStreamer - Lightweight and fast MJPEG-HTTP streamer
endef
define Package/ustreamer/install
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/ustreamer $(1)/usr/bin/
$(INSTALL_BIN) $(PKG_BUILD_DIR)/ustreamer-dump $(1)/usr/bin/
$(INSTALL_DIR) $(1)/etc/config
$(CP) ./files/ustreamer.config $(1)/etc/config/ustreamer
$(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_BIN) ./files/ustreamer.init $(1)/etc/init.d/ustreamer
endef
$(eval $(call BuildPackage,ustreamer))

View File

@@ -0,0 +1,19 @@
config ustreamer
option enabled '0'
option device '/dev/video0'
option device_timeout '5'
option input '0'
option resolution '640x480'
option format 'YUYV'
option quality '80'
option desired_fps '0'
option encoder 'CPU'
option host '::'
option port '8080'
option static ''
option user ''
option password ''

View File

@@ -0,0 +1,54 @@
#!/bin/sh /etc/rc.common
# Copyright (C) 2009-2019 OpenWrt.org
START=90
STOP=10
USE_PROCD=1
PROG=/usr/bin/ustreamer
getcfg() {
config_get value ustreamer $1 $2
return "$value"
}
start_instance() {
config_get_bool enabled ustreamer enabled 0
[ "$enabled" -eq 0 ] && return
local options=""
options="$options --device='`getcfg device /dev/video0`'"
options="$options --device-timeout='`getcfg device_timeout 5`'"
options="$options --input='`getcfg input 0`'"
options="$options --resolution='`getcfg resolution 640x480`'"
options="$options --format='`getcfg format YUYV`'"
options="$options --quality='`getcfg quality 80`'"
options="$options --desired-fps='`getcfg desired_fps 0`'"
options="$options --encoder='`getcfg encoder CPU`'"
options="$options --host='`getcfg host '::'`'"
local port=`getcfg port 8080`
options="$options --port='$port'"
options="$options --static='`getcfg static ''`'"
options="$options --user='`getcfg user ''`'"
options="$options --passwd='`getcfg password ''`'"
config-get-bool opt_slowdown ustreamer slowdown 1
[ "$slowdown" -eq 1 ] && options="$options --slowdown"
procd_open_instance
procd_set_param command "$PROG" $options
procd_add_mdns http tcp "$port" daemon=ustreamer
procd_close_instance
}
start_service() {
config_load ustreamer
config_foreach start_instance ustreamer
}
service_triggers() {
procd_add_reload_trigger ustreamer
}

20
python/Makefile Normal file
View File

@@ -0,0 +1,20 @@
-include ../config.mk
DESTDIR ?=
PREFIX ?= /usr/local
PY ?= python3
# =====
all:
$(info == PY_BUILD ustreamer-*.so)
@ $(PY) setup.py build
install:
$(PY) setup.py install --prefix=$(PREFIX) --root=$(if $(DESTDIR),$(DESTDIR),/)
clean:
rm -rf build

25
python/setup.py Normal file
View File

@@ -0,0 +1,25 @@
import os
from setuptools import Extension
from setuptools import setup
# =====
if __name__ == "__main__":
setup(
name="ustreamer",
version="5.1",
description="uStreamer tools",
author="Maxim Devaev",
author_email="mdevaev@gmail.com",
url="https://github.com/pikvm/ustreamer",
ext_modules=[
Extension(
"ustreamer",
libraries=["rt", "m", "pthread"],
undef_macros=["NDEBUG"],
sources=["src/" + name for name in os.listdir("src") if name.endswith(".c")],
depends=["src/" + name for name in os.listdir("src") if name.endswith(".h")],
),
],
)

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

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

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

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

1
python/src/memsinksh.h Symbolic link
View File

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

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

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

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

@@ -0,0 +1,318 @@
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <Python.h>
#include "tools.h"
#include "frame.h"
#include "memsinksh.h"
typedef struct {
PyObject_HEAD
char *obj;
double lock_timeout;
double wait_timeout;
double drop_same_frames;
int fd;
memsink_shared_s *mem;
uint64_t frame_id;
long double frame_ts;
frame_s *frame;
} MemsinkObject;
#define MEM(_next) self->mem->_next
#define FRAME(_next) self->frame->_next
static void MemsinkObject_destroy_internals(MemsinkObject *self) {
if (self->mem != NULL) {
memsink_shared_unmap(self->mem);
self->mem = NULL;
}
if (self->fd > 0) {
close(self->fd);
self->fd = -1;
}
if (self->frame) {
frame_destroy(self->frame);
self->frame = NULL;
}
}
static int MemsinkObject_init(MemsinkObject *self, PyObject *args, PyObject *kwargs) {
self->lock_timeout = 1;
self->wait_timeout = 1;
static char *kws[] = {"obj", "lock_timeout", "wait_timeout", "drop_same_frames", NULL};
if (!PyArg_ParseTupleAndKeywords(
args, kwargs, "s|ddd", kws,
&self->obj, &self->lock_timeout, &self->wait_timeout, &self->drop_same_frames)) {
return -1;
}
# define SET_DOUBLE(_field, _cond) { \
if (!(self->_field _cond)) { \
PyErr_SetString(PyExc_ValueError, #_field " must be " #_cond); \
return -1; \
} \
}
SET_DOUBLE(lock_timeout, > 0);
SET_DOUBLE(wait_timeout, > 0);
SET_DOUBLE(drop_same_frames, >= 0);
# undef SET_DOUBLE
self->frame = frame_init();
if ((self->fd = shm_open(self->obj, O_RDWR, 0)) == -1) {
PyErr_SetFromErrno(PyExc_OSError);
goto error;
}
if ((self->mem = memsink_shared_map(self->fd)) == NULL) {
PyErr_SetFromErrno(PyExc_OSError);
goto error;
}
return 0;
error:
MemsinkObject_destroy_internals(self);
return -1;
}
static PyObject *MemsinkObject_repr(MemsinkObject *self) {
char repr[1024];
snprintf(repr, 1023, "<Memsink(%s)>", self->obj);
return Py_BuildValue("s", repr);
}
static void MemsinkObject_dealloc(MemsinkObject *self) {
MemsinkObject_destroy_internals(self);
PyObject_Del(self);
}
static PyObject *MemsinkObject_close(MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
MemsinkObject_destroy_internals(self);
Py_RETURN_NONE;
}
static PyObject *MemsinkObject_enter(MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
Py_INCREF(self);
return (PyObject *)self;
}
static PyObject *MemsinkObject_exit(MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
return PyObject_CallMethod((PyObject *)self, "close", "");
}
static int wait_frame(MemsinkObject *self) {
long double deadline_ts = get_now_monotonic() + self->wait_timeout;
# define RETURN_OS_ERROR { \
Py_BLOCK_THREADS \
PyErr_SetFromErrno(PyExc_OSError); \
return -1; \
}
long double now;
do {
Py_BEGIN_ALLOW_THREADS
int retval = flock_timedwait_monotonic(self->fd, self->lock_timeout);
now = get_now_monotonic();
if (retval < 0 && errno != EWOULDBLOCK) {
RETURN_OS_ERROR;
} else if (retval == 0) {
if (MEM(magic) == MEMSINK_MAGIC && MEM(version) == MEMSINK_VERSION && MEM(id) != self->frame_id) {
if (self->drop_same_frames > 0) {
if (
FRAME_COMPARE_META_USED_NOTS(self->mem, self->frame)
&& (self->frame_ts + self->drop_same_frames > now)
&& !memcmp(FRAME(data), MEM(data), MEM(used))
) {
self->frame_id = MEM(id);
goto drop;
}
}
Py_BLOCK_THREADS
return 0;
}
if (flock(self->fd, LOCK_UN) < 0) {
RETURN_OS_ERROR;
}
}
drop:
if (usleep(1000) < 0) {
RETURN_OS_ERROR;
}
Py_END_ALLOW_THREADS
if (PyErr_CheckSignals() < 0) {
return -1;
}
} while (now < deadline_ts);
# undef RETURN_OS_ERROR
return -2;
}
static PyObject *MemsinkObject_wait_frame(MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
if (self->mem == NULL || self->fd <= 0) {
PyErr_SetString(PyExc_RuntimeError, "Closed");
return NULL;
}
switch (wait_frame(self)) {
case 0: break;
case -2: Py_RETURN_NONE;
default: return NULL;
}
frame_set_data(self->frame, MEM(data), MEM(used));
FRAME_COPY_META(self->mem, self->frame);
self->frame_id = MEM(id);
self->frame_ts = get_now_monotonic();
MEM(last_client_ts) = self->frame_ts;
if (flock(self->fd, LOCK_UN) < 0) {
return PyErr_SetFromErrno(PyExc_OSError);
}
PyObject *dict_frame = PyDict_New();
if (dict_frame == NULL) {
return NULL;
}
# define SET_VALUE(_key, _maker) { \
PyObject *_tmp = _maker; \
if (_tmp == NULL) { \
return NULL; \
} \
if (PyDict_SetItemString(dict_frame, _key, _tmp) < 0) { \
Py_DECREF(_tmp); \
return NULL; \
} \
Py_DECREF(_tmp); \
}
# define SET_NUMBER(_key, _from, _to) SET_VALUE(#_key, Py##_to##_From##_from(FRAME(_key)))
SET_NUMBER(width, Long, Long);
SET_NUMBER(height, Long, Long);
SET_NUMBER(format, Long, Long);
SET_NUMBER(stride, Long, Long);
SET_NUMBER(online, Long, Bool);
SET_NUMBER(key, Long, Bool);
SET_NUMBER(grab_ts, Double, Float);
SET_NUMBER(encode_begin_ts, Double, Float);
SET_NUMBER(encode_end_ts, Double, Float);
SET_VALUE("data", PyBytes_FromStringAndSize((const char *)FRAME(data), FRAME(used)));
# undef SET_NUMBER
# undef SET_VALUE
return dict_frame;
}
static PyObject *MemsinkObject_is_opened(MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
return PyBool_FromLong(self->mem != NULL && self->fd > 0);
}
#define FIELD_GETTER(_field, _from, _to) \
static PyObject *MemsinkObject_getter_##_field(MemsinkObject *self, void *Py_UNUSED(closure)) { \
return Py##_to##_From##_from(self->_field); \
}
FIELD_GETTER(obj, String, Unicode)
FIELD_GETTER(lock_timeout, Double, Float)
FIELD_GETTER(wait_timeout, Double, Float)
FIELD_GETTER(drop_same_frames, Double, Float)
#undef FIELD_GETTER
static PyMethodDef MemsinkObject_methods[] = {
# define ADD_METHOD(_name, _method, _flags) \
{.ml_name = _name, .ml_meth = (PyCFunction)MemsinkObject_##_method, .ml_flags = (_flags)}
ADD_METHOD("close", close, METH_NOARGS),
ADD_METHOD("__enter__", enter, METH_NOARGS),
ADD_METHOD("__exit__", exit, METH_VARARGS),
ADD_METHOD("wait_frame", wait_frame, METH_NOARGS),
ADD_METHOD("is_opened", is_opened, METH_NOARGS),
{},
# undef ADD_METHOD
};
static PyGetSetDef MemsinkObject_getsets[] = {
# define ADD_GETTER(_field) {.name = #_field, .get = (getter)MemsinkObject_getter_##_field}
ADD_GETTER(obj),
ADD_GETTER(lock_timeout),
ADD_GETTER(wait_timeout),
ADD_GETTER(drop_same_frames),
{},
# undef ADD_GETTER
};
static PyTypeObject MemsinkType = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "ustreamer.Memsink",
.tp_basicsize = sizeof(MemsinkObject),
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_new = PyType_GenericNew,
.tp_init = (initproc)MemsinkObject_init,
.tp_dealloc = (destructor)MemsinkObject_dealloc,
.tp_repr = (reprfunc)MemsinkObject_repr,
.tp_methods = MemsinkObject_methods,
.tp_getset = MemsinkObject_getsets,
};
static PyModuleDef ustreamer_Module = {
PyModuleDef_HEAD_INIT,
.m_name = "ustreamer",
.m_size = -1,
};
PyMODINIT_FUNC PyInit_ustreamer(void) { // cppcheck-suppress unusedFunction
PyObject *module = PyModule_Create(&ustreamer_Module);
if (module == NULL) {
return NULL;
}
if (PyType_Ready(&MemsinkType) < 0) {
return NULL;
}
Py_INCREF(&MemsinkType);
if (PyModule_AddObject(module, "Memsink", (PyObject *)&MemsinkType) < 0) {
return NULL;
}
return module;
}
#undef FRAME
#undef MEM

108
src/Makefile Normal file
View File

@@ -0,0 +1,108 @@
DESTDIR ?=
PREFIX ?= /usr/local
CC ?= gcc
CFLAGS ?= -O3
LDFLAGS ?=
# =====
_USTR = ustreamer.bin
_DUMP = ustreamer-dump.bin
_CFLAGS = -MD -c -std=c11 -Wall -Wextra -D_GNU_SOURCE $(CFLAGS)
_LDFLAGS = $(LDFLAGS)
_COMMON_LIBS = -lm -ljpeg -pthread -lrt
_USTR_LIBS = $(_COMMON_LIBS) -levent -levent_pthreads
_USTR_SRCS = $(shell ls \
libs/*.c \
ustreamer/*.c \
ustreamer/http/*.c \
ustreamer/data/*.c \
ustreamer/encoders/cpu/*.c \
ustreamer/encoders/hw/*.c \
ustreamer/h264/*.c \
)
_DUMP_LIBS = $(_COMMON_LIBS)
_DUMP_SRCS = $(shell ls \
libs/*.c \
dump/*.c \
)
_BUILD = build
define optbool
$(filter $(shell echo $(1) | tr A-Z a-z), yes on 1)
endef
ifneq ($(call optbool,$(WITH_GPIO)),)
_USTR_LIBS += -lgpiod
override _CFLAGS += -DWITH_GPIO
_USTR_SRCS += $(shell ls ustreamer/gpio/*.c)
endif
ifneq ($(call optbool,$(WITH_SYSTEMD)),)
_USTR_LIBS += -lsystemd
override _CFLAGS += -DWITH_SYSTEMD
_USTR_SRCS += $(shell ls ustreamer/http/systemd/*.c)
endif
WITH_PTHREAD_NP ?= 1
ifneq ($(call optbool,$(WITH_PTHREAD_NP)),)
override _CFLAGS += -DWITH_PTHREAD_NP
endif
WITH_SETPROCTITLE ?= 1
ifneq ($(call optbool,$(WITH_SETPROCTITLE)),)
ifeq ($(shell uname -s | tr A-Z a-z),linux)
_USTR_LIBS += -lbsd
endif
override _CFLAGS += -DWITH_SETPROCTITLE
endif
# =====
all: $(_USTR) $(_DUMP)
install: all
mkdir -p $(DESTDIR)$(PREFIX)/bin
install -m755 $(_USTR) $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_USTR))
install -m755 $(_DUMP) $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_DUMP))
install-strip: install
strip $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_USTR))
strip $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_DUMP))
$(_USTR): $(_USTR_SRCS:%.c=$(_BUILD)/%.o)
$(info == LD $@)
@ $(CC) $^ -o $@ $(_LDFLAGS) $(_USTR_LIBS)
$(_DUMP): $(_DUMP_SRCS:%.c=$(_BUILD)/%.o)
$(info == LD $@)
@ $(CC) $^ -o $@ $(_LDFLAGS) $(_DUMP_LIBS)
$(_BUILD)/%.o: %.c
$(info -- CC $<)
@ mkdir -p $(dir $@) || true
@ $(CC) $< -o $@ $(_CFLAGS)
clean:
rm -rf $(_USTR) $(_DUMP) $(_BUILD)
_OBJS = $(_USTR_SRCS:%.c=$(_BUILD)/%.o) $(_DUMP_SRCS:%.c=$(_BUILD)/%.o)
-include $(_OBJS:%.o=%.d)

View File

@@ -1,688 +0,0 @@
/*****************************************************************************
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
const unsigned BLANK_JPG_WIDTH = 640;
const unsigned BLANK_JPG_HEIGHT = 480;
const unsigned long BLANK_JPG_SIZE = 13845;
const unsigned char BLANK_JPG_DATA[] = {
0xff, 0xd8, 0xff, 0xe1, 0x9, 0x50, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65,
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x0, 0x3c, 0x3f, 0x78, 0x70, 0x61, 0x63, 0x6b,
0x65, 0x74, 0x20, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x3d, 0x22, 0xef, 0xbb, 0xbf, 0x22, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x57, 0x35,
0x4d, 0x30, 0x4d, 0x70, 0x43, 0x65, 0x68, 0x69, 0x48, 0x7a, 0x72, 0x65, 0x53, 0x7a, 0x4e, 0x54, 0x63, 0x7a, 0x6b, 0x63, 0x39,
0x64, 0x22, 0x3f, 0x3e, 0x20, 0x3c, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73,
0x3a, 0x78, 0x3d, 0x22, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x3a, 0x6e, 0x73, 0x3a, 0x6d, 0x65, 0x74, 0x61, 0x2f, 0x22, 0x20, 0x78,
0x3a, 0x78, 0x6d, 0x70, 0x74, 0x6b, 0x3d, 0x22, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x58, 0x4d, 0x50, 0x20, 0x43, 0x6f, 0x72,
0x65, 0x20, 0x35, 0x2e, 0x36, 0x2d, 0x63, 0x31, 0x33, 0x38, 0x20, 0x37, 0x39, 0x2e, 0x31, 0x35, 0x39, 0x38, 0x32, 0x34, 0x2c,
0x20, 0x32, 0x30, 0x31, 0x36, 0x2f, 0x30, 0x39, 0x2f, 0x31, 0x34, 0x2d, 0x30, 0x31, 0x3a, 0x30, 0x39, 0x3a, 0x30, 0x31, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x3e, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x20, 0x78, 0x6d,
0x6c, 0x6e, 0x73, 0x3a, 0x72, 0x64, 0x66, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x77,
0x33, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x31, 0x39, 0x39, 0x39, 0x2f, 0x30, 0x32, 0x2f, 0x32, 0x32, 0x2d, 0x72, 0x64, 0x66, 0x2d,
0x73, 0x79, 0x6e, 0x74, 0x61, 0x78, 0x2d, 0x6e, 0x73, 0x23, 0x22, 0x3e, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73,
0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x72, 0x64, 0x66, 0x3a, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x22, 0x22,
0x2f, 0x3e, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x3e, 0x20, 0x3c, 0x2f, 0x78, 0x3a, 0x78, 0x6d, 0x70,
0x6d, 0x65, 0x74, 0x61, 0x3e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x3f, 0x78, 0x70,
0x61, 0x63, 0x6b, 0x65, 0x74, 0x20, 0x65, 0x6e, 0x64, 0x3d, 0x22, 0x77, 0x22, 0x3f, 0x3e, 0xff, 0xed, 0x0, 0x2c, 0x50, 0x68,
0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x20, 0x33, 0x2e, 0x30, 0x0, 0x38, 0x42, 0x49, 0x4d, 0x4, 0x25, 0x0, 0x0, 0x0,
0x0, 0x0, 0x10, 0xd4, 0x1d, 0x8c, 0xd9, 0x8f, 0x0, 0xb2, 0x4, 0xe9, 0x80, 0x9, 0x98, 0xec, 0xf8, 0x42, 0x7e, 0xff, 0xdb,
0x0, 0x84, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1,
0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1,
0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3,
0x3, 0x3, 0x3, 0x3, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x3,
0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3,
0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3,
0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0xff, 0xdd, 0x0, 0x4, 0x0, 0x50, 0xff, 0xee, 0x0, 0xe, 0x41, 0x64, 0x6f, 0x62, 0x65,
0x0, 0x64, 0xc0, 0x0, 0x0, 0x0, 0x1, 0xff, 0xc0, 0x0, 0x11, 0x8, 0x1, 0xe0, 0x2, 0x80, 0x3, 0x0, 0x11, 0x0, 0x1,
0x11, 0x1, 0x2, 0x11, 0x1, 0xff, 0xc4, 0x0, 0x7d, 0x0, 0x1, 0x0, 0x2, 0x3, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xb, 0x7, 0x8, 0x9, 0x5, 0x6, 0x2, 0x3, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x1, 0x0, 0x1, 0x4, 0x2, 0x1, 0x3,
0x2, 0x3, 0x4, 0x8, 0x4, 0x6, 0x3, 0x0, 0x0, 0x0, 0x3, 0x2, 0x4, 0x5, 0x6, 0x1, 0x7, 0x8, 0x9, 0x11, 0x12,
0xa, 0x13, 0x14, 0x21, 0x22, 0x15, 0x37, 0x77, 0xb6, 0x16, 0x23, 0x31, 0x35, 0x39, 0x41, 0x75, 0xb4, 0x17, 0x38, 0xb5, 0xb7,
0x18, 0x1a, 0x24, 0x32, 0x51, 0x78, 0x56, 0x97, 0xd4, 0x11, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xda, 0x0, 0xc, 0x3, 0x0, 0x0, 0x1, 0x11, 0x2, 0x11, 0x0, 0x3f, 0x0, 0xaf,
0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd0,
0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff,
0xd1, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7,
0xff, 0xd2, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x7, 0xff, 0xd3, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x7, 0xff, 0xd4, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x7, 0xff, 0xd5, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x7, 0xff, 0xd6, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd7, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd0, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd1, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd2, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd3, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd4, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd5, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd6, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd7, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd0, 0xaf, 0xfc, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd1, 0xaf, 0xfc, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd2, 0xaf, 0xfc, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd3, 0xaf, 0xfc,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd4, 0xaf,
0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd5,
0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff,
0xd6, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7,
0xff, 0xd7, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x7, 0xff, 0xd0, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x7, 0xff, 0xd1, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x7, 0xff, 0xd2, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x7, 0xff, 0xd3, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x6, 0x4c, 0xe9, 0x5b, 0xb, 0x1c, 0xaf, 0x71, 0xf5, 0x2e, 0x2f, 0x29, 0x65, 0x69, 0x92, 0xc6, 0x64,
0xbb, 0x33, 0x43, 0xb0, 0xc8, 0xe3, 0xaf, 0xed, 0xa1, 0xbc, 0xb1, 0xbf, 0xb1, 0xbc, 0xda, 0x71, 0x56, 0xf7, 0x76, 0x57, 0xb6,
0x97, 0x14, 0x49, 0x6f, 0x75, 0x69, 0x75, 0x6f, 0x25, 0x54, 0x49, 0x1d, 0x74, 0xd5, 0x45, 0x74, 0x55, 0xcd, 0x35, 0x71, 0xcf,
0x1c, 0xf3, 0xc0, 0x24, 0x45, 0xeb, 0x5f, 0xe9, 0x19, 0x61, 0xd4, 0x3f, 0xb6, 0x7c, 0xbb, 0xf1, 0x67, 0x4e, 0xb6, 0xc6, 0x75,
0x4c, 0x95, 0x47, 0x71, 0xdc, 0x5d, 0x53, 0xaa, 0xe3, 0x20, 0xb3, 0xc6, 0x75, 0x94, 0xf5, 0x71, 0x15, 0xbf, 0xf4, 0xe7, 0x4d,
0xc2, 0x63, 0xa0, 0x8a, 0x1b, 0x1d, 0x2, 0xf6, 0x5f, 0x6e, 0x72, 0x56, 0x50, 0x51, 0xf6, 0xf0, 0xd3, 0xd7, 0xf7, 0xe2, 0xa6,
0x9b, 0xa, 0xeb, 0xa2, 0xc4, 0x23, 0x22, 0xd, 0xd8, 0xf4, 0xe3, 0xeb, 0xed, 0x2f, 0xb5, 0xbc, 0xe3, 0xf1, 0xa7, 0xae, 0xbb,
0x17, 0x5c, 0xc7, 0x6d, 0xba, 0x46, 0xdd, 0xd9, 0x36, 0x38, 0x7d, 0x97, 0x5b, 0xcb, 0x47, 0x5c, 0xb8, 0xdc, 0xc6, 0x36, 0x6b,
0xc, 0x85, 0x72, 0x59, 0xdd, 0xd1, 0x1c, 0x91, 0x49, 0xcc, 0x55, 0x57, 0x1d, 0x3c, 0xfe, 0x9a, 0xa9, 0xe7, 0x8e, 0x78, 0xe3,
0x9e, 0x39, 0x6, 0xf5, 0xfa, 0xf4, 0x78, 0xe9, 0xd2, 0x3e, 0x34, 0xf9, 0x4d, 0xd5, 0x7a, 0x6f, 0x44, 0x75, 0xbe, 0xbb, 0xd6,
0x3a, 0xbe, 0x6b, 0xa0, 0x30, 0xbb, 0x3e, 0x57, 0xb, 0xad, 0x45, 0x73, 0xd, 0x95, 0xee, 0x7e, 0xe3, 0xb1, 0x7b, 0x23, 0x15,
0x36, 0x52, 0x6a, 0x6e, 0xae, 0x6e, 0x6b, 0xe6, 0xea, 0x4c, 0x76, 0x26, 0xda, 0x2e, 0x79, 0xe2, 0xae, 0x38, 0xf8, 0x43, 0x4f,
0xe5, 0xff, 0x0, 0xc8, 0x6b, 0xe7, 0xa6, 0xff, 0x0, 0xa5, 0xd7, 0x6f, 0xfa, 0x83, 0xed, 0x57, 0xd7, 0xf8, 0xeb, 0xfe, 0x3a,
0xe7, 0xa3, 0xb5, 0x1c, 0x8c, 0x56, 0x3b, 0xcf, 0x6b, 0x64, 0x71, 0xf5, 0xe4, 0x3e, 0x59, 0xa, 0xa0, 0xa6, 0xeb, 0x8d, 0x57,
0x49, 0xc4, 0x73, 0x35, 0xa5, 0x1b, 0x1e, 0xd3, 0x25, 0xbc, 0x91, 0xd7, 0x3f, 0xca, 0x68, 0x6d, 0x31, 0xd6, 0xf2, 0xd3, 0x2c,
0xf5, 0xf3, 0x5d, 0x76, 0xf6, 0xf7, 0x1, 0xda, 0xee, 0xc7, 0x83, 0xd0, 0x8f, 0xd3, 0x7, 0x27, 0xff, 0x0, 0xe, 0x76, 0xe,
0xa9, 0xaf, 0xca, 0xce, 0xf4, 0xd7, 0x61, 0xae, 0xd3, 0x69, 0xc6, 0x65, 0x31, 0x98, 0xce, 0xee, 0xcb, 0x58, 0xe5, 0xe1, 0xb3,
0xbe, 0xb0, 0xa6, 0x1d, 0xea, 0xdf, 0x71, 0xc9, 0xe0, 0xba, 0x47, 0x57, 0xbf, 0xaa, 0x6b, 0xa9, 0x7f, 0x11, 0x8f, 0xb1, 0xb2,
0xfd, 0xa3, 0x67, 0x55, 0x31, 0xcd, 0x2d, 0x9f, 0xdc, 0x8e, 0xd6, 0xae, 0x43, 0x15, 0xea, 0xfe, 0xa3, 0x9e, 0x87, 0x9d, 0xd5,
0x2d, 0x3a, 0x2f, 0x73, 0xfa, 0x79, 0xeb, 0x1d, 0x25, 0x84, 0xca, 0xdf, 0x5b, 0xc1, 0x46, 0xe7, 0xab, 0x74, 0xdf, 0x5c, 0x5b,
0x59, 0x63, 0x20, 0xb8, 0xb7, 0xbb, 0xb4, 0xba, 0xbe, 0xcd, 0xec, 0x5d, 0x39, 0xce, 0xad, 0xd9, 0xd8, 0xcb, 0x7b, 0x5a, 0x2e,
0x7e, 0x54, 0xd1, 0x8c, 0xb4, 0xc8, 0xd7, 0x55, 0x5e, 0xd2, 0xf1, 0x4d, 0x32, 0x45, 0x1f, 0x20, 0xf1, 0xfc, 0xca, 0xf4, 0x2e,
0xeb, 0xad, 0xbf, 0xab, 0x6b, 0xf2, 0x77, 0xd3, 0x43, 0x7a, 0xa7, 0xb2, 0xb4, 0x6b, 0xdc, 0x3d, 0xce, 0xd3, 0x1f, 0x52, 0xf1,
0xb2, 0x45, 0xba, 0xc1, 0x9c, 0xc2, 0x5a, 0xd1, 0x27, 0x37, 0x55, 0x75, 0x6, 0xeb, 0x45, 0x73, 0x64, 0x32, 0xb9, 0x1b, 0xf,
0xc3, 0x57, 0x4d, 0x78, 0x3c, 0xbc, 0xb7, 0x59, 0x9, 0xae, 0x28, 0x96, 0x28, 0xee, 0xf9, 0xb9, 0xa6, 0x3b, 0x2a, 0xc2, 0x30,
0x12, 0x47, 0x24, 0x52, 0x57, 0x14, 0xb4, 0x57, 0x14, 0xb1, 0x57, 0x54, 0x72, 0x47, 0x25, 0x3c, 0xd1, 0x24, 0x72, 0x51, 0xcf,
0x34, 0xd7, 0x45, 0x74, 0x55, 0xc7, 0x15, 0x51, 0x5d, 0x15, 0x71, 0xcf, 0x1c, 0xf1, 0xcf, 0x1e, 0xfc, 0x72, 0xf, 0xc0, 0x3a,
0x19, 0xe9, 0xa3, 0xe0, 0x76, 0x6b, 0xd4, 0x3, 0xc8, 0x9b, 0x6e, 0xae, 0xe7, 0x33, 0x79, 0xaa, 0x75, 0xde, 0xab, 0x85, 0x97,
0x75, 0xed, 0x5d, 0xbb, 0x1f, 0x14, 0x32, 0x64, 0xf1, 0x7a, 0xad, 0xb5, 0xf5, 0x9e, 0x3a, 0x1c, 0x56, 0xbd, 0xc5, 0xe4, 0x17,
0x18, 0xfa, 0xb6, 0x8d, 0x8f, 0x25, 0x7d, 0x1d, 0xbd, 0xa7, 0xdf, 0xa2, 0xb8, 0xe0, 0x8b, 0x89, 0xee, 0xaa, 0x8e, 0x6a, 0x6d,
0xea, 0x86, 0xb0, 0x90, 0x37, 0x92, 0x7d, 0xb9, 0xe8, 0xb9, 0xe9, 0x95, 0x9b, 0x8f, 0xc7, 0x5b, 0xf, 0xc, 0x74, 0x8f, 0x21,
0x3b, 0x3f, 0x9, 0x69, 0x8c, 0x9f, 0x70, 0xb4, 0xcb, 0xe9, 0x1a, 0x5f, 0x67, 0xdf, 0xeb, 0x52, 0x5e, 0x5b, 0x71, 0x91, 0x86,
0x9d, 0xc3, 0xb1, 0x7b, 0x82, 0xbc, 0xfd, 0xf5, 0xae, 0xc9, 0x95, 0xb5, 0xc8, 0x53, 0x77, 0xc6, 0x3b, 0x19, 0xc, 0x90, 0x43,
0xc, 0x94, 0x53, 0x5d, 0x36, 0x91, 0xd3, 0x6f, 0x10, 0x3f, 0x5b, 0xf, 0x40, 0xfa, 0x35, 0xfa, 0x99, 0x78, 0xcf, 0xda, 0x3d,
0xd7, 0xd2, 0xb0, 0x6a, 0x9e, 0x21, 0x6d, 0x5d, 0x51, 0x87, 0x97, 0x2b, 0xb9, 0x6c, 0x18, 0xdd, 0x73, 0xf, 0xd6, 0x37, 0xbd,
0x51, 0x27, 0x18, 0xda, 0xb8, 0xc4, 0xcb, 0xd8, 0xfd, 0x51, 0xaf, 0xe4, 0xb9, 0xeb, 0xfd, 0x9b, 0x4f, 0xce, 0xf3, 0x8e, 0xa7,
0xed, 0x5c, 0x62, 0x6a, 0xaa, 0x6b, 0xdb, 0x88, 0xe5, 0x86, 0xd6, 0xfa, 0x3b, 0xde, 0x6e, 0xa1, 0xe4, 0x38, 0x81, 0xe8, 0xdf,
0xd6, 0xdd, 0x77, 0xd8, 0xfe, 0xa4, 0x7d, 0x35, 0xd7, 0xdd, 0x89, 0xaa, 0xe8, 0xfd, 0xb3, 0xa1, 0xde, 0xdb, 0x77, 0xc, 0x77,
0xd8, 0x3d, 0xc3, 0x56, 0xb1, 0xda, 0x74, 0xdd, 0x8e, 0x8c, 0x3f, 0x53, 0xef, 0x97, 0xd8, 0x9c, 0x8c, 0xda, 0xde, 0xe1, 0x88,
0xae, 0x1b, 0x88, 0x63, 0xbe, 0xb2, 0x86, 0xf2, 0xd7, 0x8b, 0xbb, 0x3a, 0x27, 0x86, 0x5a, 0x28, 0xaf, 0x9a, 0x23, 0x96, 0x9f,
0x6a, 0x43, 0xeb, 0xfd, 0x72, 0xfa, 0xcb, 0xad, 0xfa, 0x93, 0xcf, 0x7d, 0x8f, 0x4e, 0xea, 0x9e, 0xbe, 0xd2, 0x3a, 0xcb, 0x51,
0x83, 0xac, 0xfa, 0xda, 0xfe, 0xd, 0x5b, 0xaf, 0x75, 0x3c, 0xe, 0x97, 0xae, 0x43, 0x7d, 0x7d, 0x8b, 0xba, 0x92, 0xfa, 0xf6,
0x2c, 0x1e, 0xb9, 0x61, 0x8d, 0xc6, 0x47, 0x77, 0x79, 0x25, 0x3c, 0x55, 0x2c, 0x9c, 0x45, 0xc5, 0x72, 0x73, 0xc7, 0xbd, 0x5c,
0xf3, 0xc8, 0x35, 0xa7, 0xc0, 0x5f, 0x4f, 0xee, 0xe3, 0xf5, 0x1, 0xed, 0x59, 0xf4, 0x3e, 0xba, 0xae, 0xdb, 0x58, 0xd3, 0xf5,
0x98, 0x6d, 0x32, 0x5d, 0x99, 0xda, 0x59, 0xab, 0x39, 0xef, 0x35, 0xed, 0x17, 0x11, 0x7b, 0x24, 0xd4, 0x59, 0x51, 0xc5, 0x94,
0x13, 0x5a, 0xcd, 0x9f, 0xd9, 0xf3, 0x55, 0x5b, 0x4b, 0x46, 0x3b, 0x17, 0x14, 0xd0, 0xd7, 0x73, 0x54, 0x52, 0x57, 0x24, 0xb0,
0x5b, 0x45, 0x3d, 0xc4, 0x41, 0x22, 0xce, 0xc0, 0xea, 0xf, 0x43, 0xcf, 0x4a, 0x3b, 0x4c, 0x56, 0xa9, 0xdc, 0xda, 0x87, 0xfe,
0x24, 0x7b, 0xd2, 0x28, 0xac, 0xef, 0xf2, 0x9a, 0xee, 0xc5, 0x8c, 0xb0, 0xee, 0x6e, 0xc0, 0xb8, 0x8e, 0x4b, 0x39, 0x66, 0x8a,
0xef, 0x33, 0xa2, 0xe5, 0x32, 0x1a, 0xff, 0x0, 0x4c, 0xe8, 0xf8, 0xa9, 0x69, 0xbc, 0xf9, 0xda, 0xda, 0xe4, 0x28, 0xb3, 0xbc,
0xbb, 0x86, 0x68, 0xa4, 0xf7, 0xbc, 0xa6, 0x1f, 0xbf, 0x18, 0x60, 0xad, 0x2f, 0xd4, 0xd7, 0xd1, 0x2f, 0xb3, 0xaf, 0xe6, 0xd3,
0x3b, 0x67, 0xd3, 0x9f, 0x48, 0xe9, 0xdd, 0x77, 0x31, 0x73, 0x61, 0x4, 0x7b, 0xb6, 0x1b, 0xa1, 0x7a, 0x7f, 0x23, 0x6f, 0x8c,
0x8a, 0x2b, 0x9a, 0xae, 0xe6, 0xbc, 0xcc, 0xe4, 0x3a, 0xda, 0xc7, 0xb, 0xd8, 0x98, 0x4b, 0x68, 0x6b, 0xb5, 0x86, 0x9e, 0x78,
0xc3, 0xdb, 0xe4, 0xa6, 0xb9, 0xa2, 0x49, 0x22, 0x92, 0x8e, 0x22, 0xf9, 0x71, 0x20, 0x63, 0x8f, 0x52, 0xdf, 0x4b, 0xcf, 0x7,
0x35, 0x5f, 0x19, 0x24, 0xf3, 0x77, 0xc3, 0x5e, 0xee, 0xd6, 0x35, 0x8d, 0x6, 0xe3, 0x9c, 0x75, 0x38, 0x9d, 0x16, 0xf7, 0x74,
0x9f, 0x75, 0xd1, 0xbb, 0xa, 0xea, 0xfe, 0xea, 0x58, 0xeb, 0xc2, 0x75, 0xb6, 0xc7, 0x7d, 0x79, 0x95, 0xdb, 0x71, 0xdb, 0xd5,
0xad, 0x31, 0xcf, 0x54, 0xd8, 0x8b, 0xd9, 0x6f, 0x78, 0xa7, 0x9b, 0x49, 0x63, 0x93, 0xf0, 0x1c, 0xc1, 0x2f, 0x20, 0xc1, 0x3e,
0x85, 0x7e, 0x2d, 0x74, 0x8f, 0x97, 0x1b, 0x57, 0x96, 0x9d, 0x59, 0xdd, 0xfa, 0x46, 0x1b, 0x6a, 0xc3, 0xe4, 0x3a, 0x53, 0xb,
0x6f, 0x84, 0xcd, 0x4f, 0x8e, 0xc7, 0x49, 0xb5, 0xe8, 0x99, 0x6c, 0x86, 0xcf, 0x55, 0xbd, 0x1b, 0x4e, 0x85, 0x9f, 0xbb, 0xb3,
0xba, 0xbb, 0xd6, 0x76, 0x4b, 0x2a, 0xa9, 0xa2, 0xaa, 0x67, 0x83, 0xf4, 0x4d, 0x4d, 0x1f, 0x66, 0xe2, 0x89, 0xad, 0xeb, 0x92,
0x1a, 0xc3, 0x9d, 0x3e, 0x77, 0x78, 0x45, 0xda, 0x3e, 0x7, 0xf7, 0x96, 0x5b, 0xa9, 0xbb, 0x2, 0x19, 0x72, 0x9a, 0xfd, 0xf7,
0x17, 0x19, 0xae, 0xb2, 0xec, 0x2b, 0x7b, 0x4a, 0xe0, 0xc2, 0x76, 0xe, 0x9d, 0xcd, 0xcd, 0x51, 0x5b, 0x64, 0xed, 0x3f, 0x54,
0xb1, 0xd9, 0x66, 0xf1, 0xd5, 0x73, 0x4c, 0x19, 0x4c, 0x7d, 0x55, 0xd5, 0x2d, 0x95, 0xd7, 0xf9, 0xd7, 0x4, 0xb6, 0xf3, 0xcc,
0x1a, 0x5a, 0x9, 0x44, 0xfa, 0x3, 0xf8, 0x73, 0xe3, 0xf, 0x93, 0x7d, 0x45, 0xe4, 0x6, 0x6f, 0xbe, 0xfa, 0x5f, 0x4d, 0xed,
0xc, 0xb6, 0xaf, 0xd8, 0xfa, 0xc6, 0x2b, 0x5f, 0xbf, 0xd9, 0xad, 0xaf, 0x26, 0xb8, 0xc5, 0xe3, 0xaf, 0x75, 0x89, 0x6e, 0xee,
0xad, 0x2d, 0xab, 0xb5, 0xbc, 0xb5, 0xf6, 0x86, 0x6b, 0x9a, 0x38, 0xaf, 0x9e, 0x2a, 0xe2, 0xaf, 0xd5, 0xfd, 0x9e, 0xde, 0xfc,
0xfb, 0x84, 0x62, 0x33, 0x10, 0xc7, 0x6f, 0x97, 0xca, 0xc1, 0xd, 0x1c, 0x47, 0xc, 0x19, 0x2b, 0xe8, 0x62, 0x8e, 0x9f, 0x7f,
0x6a, 0x23, 0x8e, 0xe6, 0x5a, 0x23, 0xa3, 0x8f, 0x7f, 0x7e, 0x7d, 0xa9, 0xa6, 0x9e, 0x38, 0x7, 0x9a, 0x9, 0x48, 0xfa, 0x14,
0x7a, 0x66, 0xf5, 0x9f, 0x79, 0xf4, 0xf7, 0x72, 0x79, 0x5, 0xe4, 0x87, 0x5e, 0x61, 0x77, 0x3d, 0x47, 0xb2, 0xb1, 0x99, 0xde,
0x9b, 0xea, 0x5c, 0x5e, 0xc9, 0x88, 0xb0, 0xc8, 0xfe, 0x3, 0x1b, 0x17, 0xce, 0xdb, 0x7e, 0xec, 0xdd, 0x66, 0x4c, 0x84, 0x17,
0xb1, 0xe2, 0x76, 0x5b, 0x4c, 0xd5, 0x11, 0x63, 0x30, 0xd9, 0x6b, 0x7a, 0x62, 0xbf, 0xc6, 0x5d, 0x63, 0xef, 0xf9, 0x8a, 0xba,
0x7e, 0xe7, 0x1c, 0x82, 0x3d, 0xde, 0x53, 0x78, 0xf1, 0xb9, 0x78, 0xa7, 0xdf, 0xfd, 0x9f, 0xd0, 0x7b, 0xd4, 0x33, 0x7e, 0xd8,
0xeb, 0xdd, 0x9a, 0xf7, 0x17, 0x67, 0x93, 0x92, 0xd6, 0x4b, 0x4b, 0x7d, 0x9b, 0x5b, 0x96, 0xaf, 0xc5, 0xea, 0xdb, 0x6e, 0x3a,
0x29, 0x3d, 0xf9, 0xe3, 0x1d, 0xb3, 0xe0, 0x27, 0xb7, 0xbd, 0x8b, 0x8f, 0x7e, 0x79, 0x8f, 0x89, 0xbe, 0xdd, 0x5e, 0xd5, 0xd1,
0x57, 0x1c, 0x6, 0xbf, 0x82, 0x57, 0x7f, 0x4e, 0xff, 0x0, 0x8e, 0x9e, 0x3e, 0x77, 0x67, 0x4e, 0x79, 0x17, 0x94, 0xee, 0x5e,
0x8a, 0xe9, 0xbe, 0xdb, 0xc9, 0xe1, 0x3b, 0x33, 0x54, 0xb0, 0xc2, 0xe4, 0x7b, 0x3b, 0xac, 0x74, 0x9d, 0xf6, 0xfb, 0x11, 0x63,
0x71, 0xab, 0x4f, 0x71, 0x71, 0x65, 0x8b, 0xbb, 0xda, 0xb0, 0x79, 0x6b, 0x8c, 0x7d, 0xa4, 0xf7, 0x1c, 0x71, 0x5d, 0x71, 0xc5,
0x55, 0x14, 0x55, 0x5f, 0x1f, 0x2e, 0x78, 0xe7, 0x9f, 0xcc, 0x11, 0x57, 0xcd, 0xc7, 0x1c, 0x59, 0x9c, 0xbc, 0x51, 0x51, 0x44,
0x51, 0x45, 0x93, 0xbf, 0x8e, 0x38, 0xe3, 0xa7, 0x8a, 0x23, 0x8e, 0x3a, 0x2e, 0xa5, 0xa6, 0x8a, 0x28, 0xa2, 0x9e, 0x38, 0xa6,
0x8a, 0x28, 0xa7, 0x8e, 0x38, 0xe3, 0x8e, 0x38, 0xf6, 0xe3, 0x80, 0x79, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xd4, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x57, 0xe8, 0x6f, 0xdf, 0x97, 0x4c, 0xff, 0x0, 0x15, 0xfa,
0xef, 0xf9, 0xbf, 0xe, 0xb, 0xf, 0x7c, 0xc4, 0xf3, 0x8b, 0xaa, 0x7c, 0x4d, 0xed, 0xf, 0x1b, 0x7a, 0xdb, 0xbc, 0xec, 0x31,
0x90, 0x75, 0x47, 0x93, 0x95, 0xf6, 0x96, 0x95, 0x9e, 0xdd, 0x33, 0x1f, 0x66, 0x6c, 0x26, 0x9f, 0x96, 0xc1, 0x45, 0xa0, 0xc1,
0x84, 0xe7, 0x6d, 0xb2, 0xbb, 0xa2, 0x4b, 0x9, 0x34, 0x5d, 0x82, 0x3d, 0xbe, 0xea, 0xd3, 0x29, 0x3c, 0xdc, 0x73, 0x1d, 0x9f,
0x15, 0x45, 0x34, 0xbe, 0xd6, 0xb4, 0xdc, 0x55, 0x48, 0x45, 0xf, 0xd6, 0x3b, 0xd2, 0xb2, 0xef, 0xc3, 0xfd, 0xba, 0x6e, 0xfe,
0xe8, 0xec, 0x54, 0xf9, 0x2f, 0x17, 0x7b, 0xb, 0x33, 0xf2, 0x92, 0xca, 0xc2, 0x9a, 0xee, 0xff, 0x0, 0xe0, 0xbe, 0xd1, 0x99,
0x9a, 0xb9, 0xad, 0xb5, 0x8b, 0xe9, 0x69, 0xaa, 0x5a, 0xeb, 0xd1, 0x33, 0x32, 0x57, 0xed, 0x83, 0xc8, 0x57, 0xcf, 0x34, 0xc3,
0x5f, 0x3c, 0x58, 0x5c, 0x55, 0xc4, 0xdf, 0x84, 0x96, 0xf4, 0x34, 0xc3, 0xd2, 0x8b, 0xfc, 0x46, 0x3c, 0x46, 0xfe, 0x2d, 0xe3,
0x3f, 0xe9, 0xd9, 0x30, 0x74, 0xff, 0x0, 0xea, 0x34, 0xd7, 0x72, 0x9b, 0x7f, 0x9e, 0x1e, 0x3b, 0x6a, 0x78, 0x48, 0xa8, 0xb8,
0xcd, 0x6d, 0x1e, 0x3f, 0xe9, 0x3a, 0xee, 0x22, 0x9, 0x24, 0xa6, 0x18, 0xe6, 0xca, 0x66, 0xbb, 0x93, 0xb4, 0x71, 0xb8, 0xf8,
0xab, 0x96, 0xaf, 0xd3, 0x15, 0x12, 0x5d, 0xdc, 0xd1, 0xc7, 0x35, 0x73, 0xf9, 0x53, 0xc7, 0x3e, 0xe0, 0xea, 0xf7, 0xa8, 0x47,
0x67, 0xd9, 0xfa, 0x4a, 0xfa, 0x63, 0xf5, 0xff, 0x0, 0x49, 0x78, 0xf7, 0x3c, 0x78, 0x1d, 0xeb, 0x3d, 0x6, 0x2f, 0xa5, 0xb4,
0xcd, 0xa2, 0xce, 0xde, 0x2b, 0x7c, 0x95, 0xae, 0x46, 0xfb, 0x15, 0x7f, 0x9e, 0xed, 0x4e, 0xd8, 0xae, 0x88, 0xfe, 0xd7, 0x1c,
0x6c, 0xb9, 0x4a, 0xa1, 0xbb, 0x96, 0x39, 0xe9, 0xe7, 0xe5, 0x6b, 0x95, 0xcb, 0x43, 0x35, 0x34, 0xf3, 0x44, 0x3f, 0x0, 0x41,
0x76, 0x69, 0xa6, 0xb9, 0x9a, 0x5b, 0x8b, 0x89, 0x64, 0x9e, 0xe2, 0x79, 0x2b, 0x9a, 0x79, 0xe6, 0xae, 0xb9, 0x66, 0x9a, 0x69,
0x6b, 0xe6, 0xb9, 0x65, 0x96, 0x5a, 0xf9, 0xaa, 0xb9, 0x24, 0x92, 0xba, 0xb9, 0xe6, 0xaa, 0xb9, 0xe7, 0x9e, 0x79, 0xe7, 0x9f,
0x7e, 0x41, 0xfc, 0xc1, 0xdf, 0xbf, 0x40, 0x9f, 0x36, 0x76, 0xbe, 0x97, 0xf2, 0x73, 0x13, 0xe3, 0xe, 0xc5, 0x99, 0xbc, 0xbc,
0xe9, 0xdf, 0x21, 0x6e, 0xae, 0xb1, 0xd8, 0xec, 0x3d, 0xd4, 0xf2, 0x4d, 0x65, 0xa9, 0x76, 0xbd, 0xae, 0x36, 0x7b, 0xbd, 0x73,
0x3f, 0x88, 0x8a, 0x49, 0x79, 0xa2, 0xc3, 0x8d, 0xae, 0x3b, 0x1e, 0x71, 0x17, 0xf1, 0xc3, 0x47, 0x1f, 0x8b, 0x96, 0x6b, 0x29,
0x24, 0xe7, 0xda, 0xd6, 0x90, 0x7c, 0x6f, 0xaf, 0xbf, 0x8b, 0x9a, 0xf7, 0x40, 0x79, 0x9f, 0x47, 0x60, 0x69, 0x58, 0xeb, 0x6c,
0x4e, 0xa9, 0xe4, 0x7e, 0xb3, 0x37, 0x65, 0xdd, 0xe3, 0x2d, 0x22, 0xae, 0x1b, 0x5b, 0x2e, 0xc5, 0xb5, 0xcb, 0x5c, 0xe2, 0xfb,
0xe, 0xab, 0x68, 0xfe, 0x1c, 0xc5, 0xf6, 0xf3, 0x97, 0x75, 0x5a, 0x66, 0x26, 0xe7, 0x8a, 0xf9, 0xe7, 0x9b, 0xdc, 0x9c, 0xfc,
0x7c, 0x28, 0xa3, 0x88, 0xf8, 0xa8, 0x38, 0x6c, 0x9, 0x2c, 0x7d, 0x34, 0x7d, 0x9b, 0xa8, 0x6b, 0x9e, 0x41, 0xf7, 0xf7, 0x58,
0x66, 0xb2, 0x56, 0x76, 0x1b, 0x5f, 0x66, 0xf5, 0xbe, 0xb5, 0x95, 0xd2, 0xed, 0xee, 0xe6, 0xa2, 0x9, 0x33, 0x55, 0x75, 0xf6,
0x5f, 0x31, 0x75, 0xb0, 0xe2, 0x71, 0xbc, 0x49, 0x4f, 0x1f, 0x8b, 0xc9, 0x51, 0x8b, 0xd8, 0xf8, 0xbe, 0xfb, 0x14, 0x55, 0xf7,
0x39, 0xb4, 0xb2, 0x9e, 0x5e, 0x29, 0xaa, 0x88, 0x64, 0xaa, 0x80, 0xd3, 0x5f, 0x58, 0x8f, 0x7, 0x7c, 0x87, 0xe8, 0xcf, 0x2b,
0xbb, 0xcb, 0xbb, 0x36, 0x2d, 0x4f, 0x66, 0xda, 0x7a, 0x67, 0xb8, 0xbb, 0x2f, 0x69, 0xec, 0x6d, 0x5f, 0xb5, 0xf1, 0x76, 0x17,
0xb9, 0x8d, 0x67, 0x19, 0x1e, 0xe9, 0x97, 0xb9, 0xce, 0x53, 0xa5, 0x6c, 0xf9, 0x3b, 0x68, 0xa6, 0x8b, 0x54, 0xcb, 0xeb, 0x72,
0xde, 0x57, 0x61, 0x69, 0x6f, 0x7d, 0xcc, 0x1c, 0x5e, 0x5a, 0x5a, 0x53, 0x25, 0xaf, 0x32, 0x51, 0x4d, 0x7f, 0x6c, 0x38, 0xe2,
0xe, 0xbd, 0x7a, 0x12, 0xff, 0x0, 0x89, 0xef, 0x8f, 0xbf, 0xe9, 0x3d, 0xcb, 0xff, 0x0, 0x64, 0xfb, 0x4, 0x19, 0x7, 0xea,
0xd, 0xff, 0x0, 0x11, 0xad, 0xa3, 0xf8, 0x51, 0xd5, 0x9f, 0xf4, 0x8b, 0xc0, 0x77, 0x77, 0x44, 0xbf, 0xc6, 0x7a, 0x47, 0xfa,
0x2e, 0xe3, 0xbb, 0x13, 0x5d, 0xc2, 0xd9, 0xc3, 0xdc, 0x7b, 0x2e, 0x83, 0xae, 0x6e, 0x57, 0x12, 0x5d, 0x58, 0x5b, 0xcd, 0x71,
0x91, 0xef, 0x7e, 0xf1, 0x87, 0x17, 0x4e, 0x1e, 0x4d, 0x82, 0x2e, 0x7d, 0xad, 0xf2, 0x36, 0x9d, 0x69, 0x8f, 0xc9, 0x5b, 0xc3,
0x24, 0x55, 0x57, 0xc5, 0x17, 0x16, 0x18, 0x1e, 0x63, 0xa7, 0x9e, 0x6b, 0x97, 0xde, 0xa0, 0x84, 0x2e, 0xd1, 0xb4, 0x6c, 0x9b,
0xbe, 0xc9, 0x9d, 0xdc, 0x37, 0xc, 0xee, 0x57, 0x67, 0xda, 0xb6, 0x7c, 0xad, 0xf6, 0x73, 0x61, 0xd8, 0x73, 0x97, 0xd7, 0x19,
0x2c, 0xc6, 0x6b, 0x31, 0x92, 0xb8, 0x92, 0xea, 0xff, 0x0, 0x25, 0x92, 0xbf, 0xba, 0x92, 0x5b, 0x8b, 0xbb, 0xcb, 0xbb, 0x89,
0x6a, 0xae, 0xba, 0xeb, 0xab, 0x9a, 0xaa, 0xab, 0x90, 0x78, 0x20, 0xf4, 0x79, 0xcc, 0x65, 0xaa, 0xc4, 0xc7, 0x80, 0xab, 0x29,
0x91, 0xab, 0x5, 0x16, 0x46, 0x6c, 0xc4, 0x58, 0x5e, 0x6f, 0x6e, 0x79, 0xc4, 0xc7, 0x96, 0xb8, 0xb6, 0x82, 0xca, 0x7c, 0xa4,
0x78, 0xee, 0x65, 0xfc, 0x1d, 0x19, 0x19, 0xec, 0xed, 0xa3, 0x8a, 0xb9, 0xf8, 0xa3, 0x89, 0x6a, 0x8a, 0x3a, 0x69, 0xe6, 0xae,
0x69, 0xa7, 0x8e, 0x38, 0x9, 0x2e, 0x7d, 0x32, 0x7f, 0xbf, 0x7f, 0x26, 0xff, 0x0, 0x84, 0x9a, 0x9f, 0xf3, 0x8d, 0x40, 0xeb,
0x6e, 0xed, 0xb1, 0xf8, 0xbf, 0xea, 0xf5, 0x8f, 0xf2, 0xb7, 0xc1, 0xae, 0xcd, 0x82, 0xcb, 0x47, 0xef, 0xbf, 0x1b, 0xbb, 0x6b,
0xb2, 0xf0, 0x1a, 0x96, 0x46, 0x3a, 0x60, 0xbc, 0xd8, 0xf0, 0xf6, 0x7a, 0x96, 0xdf, 0x93, 0xd6, 0x75, 0x5e, 0xe0, 0xd1, 0x3f,
0x15, 0x25, 0xbc, 0xd9, 0x7c, 0x3d, 0xd5, 0xbd, 0xbd, 0xbd, 0x86, 0xd1, 0x8b, 0xe2, 0x4a, 0x69, 0xe2, 0x49, 0x7e, 0x15, 0xf3,
0xd, 0x37, 0x16, 0x17, 0x14, 0x84, 0x28, 0x7c, 0x99, 0xf1, 0xaf, 0xb5, 0x7c, 0x4a, 0xee, 0x5d, 0xbb, 0xa3, 0xbb, 0x8b, 0x7,
0x56, 0x1b, 0x6d, 0xd5, 0x6e, 0xb8, 0xe6, 0xb, 0xbb, 0x7f, 0xbb, 0x36, 0xb, 0x69, 0xc0, 0x5d, 0x55, 0x25, 0x58, 0x5d, 0xbf,
0x55, 0xc8, 0xc9, 0x14, 0x3c, 0x65, 0x35, 0xcc, 0xed, 0xb4, 0x7c, 0xd7, 0x4, 0xbf, 0x1a, 0x24, 0x8a, 0x4a, 0x64, 0xb7, 0x9e,
0x88, 0x6e, 0x61, 0x9a, 0x18, 0xc2, 0x54, 0x7f, 0x4c, 0x97, 0xee, 0x37, 0xc9, 0xef, 0xe2, 0xbe, 0x9d, 0xfc, 0xa1, 0x70, 0x8,
0x7b, 0x67, 0xff, 0x0, 0xbf, 0x73, 0x5f, 0xea, 0xd9, 0x1f, 0xf7, 0x93, 0x3, 0x28, 0xf8, 0xed, 0xd1, 0x9b, 0x97, 0x92, 0xdd,
0xdf, 0xd6, 0x5d, 0x13, 0xa0, 0xc1, 0xcc, 0xbb, 0x47, 0x65, 0xed, 0x78, 0xdd, 0x72, 0xd2, 0xe3, 0x98, 0x6a, 0xb8, 0xb7, 0xc3,
0xd8, 0xcf, 0x5f, 0x33, 0xe7, 0x36, 0x4c, 0x84, 0x54, 0x57, 0x1d, 0x75, 0x62, 0xb5, 0x8c, 0x1d, 0xbd, 0xce, 0x42, 0xef, 0xe3,
0x57, 0x15, 0x7e, 0x1a, 0xda, 0xbf, 0x8f, 0xbd, 0x5e, 0xdc, 0x72, 0x12, 0xe5, 0xf5, 0x32, 0xf3, 0xa6, 0xc7, 0xd2, 0xdb, 0x59,
0xf0, 0xcb, 0xc4, 0xdf, 0x18, 0x78, 0x86, 0xde, 0xeb, 0xaf, 0x26, 0xd1, 0xb7, 0x2d, 0xdf, 0x9, 0xc4, 0xf1, 0xf1, 0x3d, 0xff,
0x0, 0x4c, 0x68, 0xd7, 0x3f, 0xb2, 0xe0, 0xd3, 0xf3, 0xf7, 0x54, 0x53, 0x54, 0x94, 0xdf, 0xf7, 0x16, 0x56, 0xda, 0xfe, 0x6c,
0x85, 0xdf, 0x14, 0xf1, 0x71, 0xff, 0x0, 0xa4, 0x92, 0x5e, 0x7f, 0x3b, 0x9e, 0x2a, 0xe4, 0x30, 0xe7, 0xaf, 0x17, 0x8f, 0x9a,
0x8f, 0x92, 0xfe, 0x35, 0xf4, 0xaf, 0xa8, 0xff, 0x0, 0x47, 0x51, 0x46, 0x77, 0x1f, 0x8c, 0xd5, 0x75, 0x8b, 0x4d, 0xc7, 0x23,
0x65, 0x6f, 0x4f, 0x17, 0x39, 0x7e, 0xa0, 0xdf, 0x2b, 0x8b, 0x23, 0xa5, 0x67, 0xf2, 0x50, 0xc3, 0xf7, 0x6b, 0x82, 0xfb, 0x49,
0xda, 0x33, 0x35, 0x59, 0x5d, 0xc5, 0x57, 0x3c, 0xcb, 0x7, 0x19, 0x6a, 0xe9, 0x97, 0x9a, 0x78, 0xb4, 0xe7, 0x8a, 0x42, 0x22,
0xc0, 0x98, 0xff, 0x0, 0xd3, 0x25, 0xfb, 0x8d, 0xf2, 0x7b, 0xf8, 0xaf, 0xa7, 0x7f, 0x28, 0x5c, 0x2, 0x1e, 0xd9, 0xff, 0x0,
0xef, 0xdc, 0xd7, 0xfa, 0xb6, 0x47, 0xfd, 0xe4, 0xc0, 0xf2, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xff, 0xd5, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x57, 0xe8, 0x6f, 0xdf, 0x97, 0x4c, 0xff, 0x0, 0x15, 0xfa, 0xef, 0xf9,
0xbf, 0xe, 0x9, 0x43, 0xfd, 0x4f, 0xff, 0x0, 0xdc, 0x5e, 0x17, 0x7f, 0xab, 0x77, 0xf7, 0xfb, 0x3e, 0x9d, 0x6, 0x35, 0xf4,
0x75, 0xf5, 0x2c, 0xd3, 0xfb, 0x1b, 0x50, 0x87, 0xd3, 0x8f, 0xcd, 0x6a, 0xf1, 0x7b, 0x76, 0x89, 0xba, 0x62, 0x64, 0xd0, 0xfa,
0x83, 0x66, 0xde, 0x64, 0xa6, 0xf3, 0x17, 0x90, 0xc5, 0x5f, 0xdb, 0xd1, 0x63, 0x6b, 0xd2, 0x7b, 0x75, 0xcd, 0xe5, 0x5c, 0x7c,
0x6d, 0xa4, 0xa3, 0x8e, 0x28, 0xd6, 0x6f, 0x2b, 0x92, 0x99, 0x20, 0x9b, 0x8a, 0x2c, 0x28, 0xae, 0x9e, 0x78, 0xb0, 0xa6, 0x80,
0xf8, 0x7c, 0x7f, 0xa6, 0xa6, 0xe3, 0xe0, 0x17, 0xab, 0x8f, 0x89, 0x17, 0x78, 0x58, 0xf2, 0x5b, 0x27, 0x8e, 0x9d, 0x85, 0xde,
0x11, 0x4b, 0xd5, 0x1b, 0xc4, 0xd4, 0x57, 0x71, 0x3e, 0x2a, 0x4e, 0x71, 0x99, 0x7b, 0xd9, 0x3a, 0xe7, 0x72, 0xb9, 0xa2, 0x2a,
0x61, 0x83, 0x6d, 0xc1, 0xda, 0xd3, 0x5f, 0xd8, 0x9b, 0xf4, 0xc7, 0x98, 0xb1, 0x8b, 0xf1, 0x51, 0x53, 0x4c, 0x94, 0x5d, 0x5b,
0xda, 0x87, 0xd9, 0x7a, 0xe9, 0x6c, 0xb8, 0x9d, 0x2f, 0xd5, 0x33, 0xc2, 0x3d, 0xc7, 0x3d, 0x2c, 0x30, 0x60, 0xf5, 0x3d, 0x17,
0xa4, 0xb6, 0x5c, 0xcc, 0xf7, 0x32, 0xc7, 0x5, 0xbc, 0x38, 0x9c, 0x17, 0x91, 0x5b, 0xee, 0x53, 0x23, 0x2c, 0xf3, 0x4d, 0xfd,
0x4c, 0x30, 0xc7, 0x67, 0x6b, 0x5f, 0x35, 0x55, 0x5f, 0xe9, 0xa6, 0x9e, 0x39, 0xe7, 0x9f, 0xc8, 0x1b, 0xf, 0xf5, 0x36, 0x68,
0xf9, 0xcc, 0xb7, 0x4d, 0xf8, 0xb7, 0xd9, 0x56, 0x34, 0x49, 0x36, 0xb5, 0xa8, 0x76, 0x26, 0xff, 0x0, 0xa9, 0xe6, 0x64, 0x86,
0x3a, 0xe5, 0x86, 0x2c, 0x87, 0x60, 0x6b, 0x9a, 0xfe, 0x57, 0x1, 0x71, 0x34, 0xb4, 0x55, 0xcc, 0x71, 0x45, 0x54, 0x3d, 0x7f,
0x7b, 0x45, 0x35, 0x55, 0xc7, 0xb5, 0x55, 0x49, 0xc7, 0x1c, 0x55, 0xc7, 0x3c, 0xfb, 0x54, 0x10, 0xea, 0x0, 0x1b, 0xdd, 0xe9,
0x87, 0xa2, 0xe7, 0xfb, 0xf, 0xd4, 0x1b, 0xc4, 0x1c, 0x1e, 0xb9, 0x1c, 0xf2, 0x5e, 0xe2, 0xfb, 0xdb, 0x41, 0xde, 0xaf, 0x79,
0x82, 0x89, 0xab, 0xe6, 0x2c, 0x7, 0x5a, 0x66, 0xad, 0xfb, 0xb, 0x66, 0x92, 0x5e, 0x60, 0xa6, 0xaa, 0xa8, 0x83, 0xfa, 0x3f,
0xac, 0x5c, 0xf1, 0x5d, 0x55, 0x7b, 0x51, 0xed, 0x57, 0xea, 0xe7, 0x8e, 0x39, 0xe4, 0x1d, 0x98, 0xfa, 0x9c, 0xf6, 0x9c, 0x35,
0xdf, 0x6b, 0x78, 0xa1, 0xa4, 0xc1, 0x24, 0x1c, 0xec, 0x1a, 0xff, 0x0, 0x5e, 0xf6, 0x56, 0xd3, 0x93, 0x86, 0x9f, 0x87, 0xe2,
0x68, 0xc3, 0x6e, 0x3b, 0x26, 0xb5, 0x89, 0xc1, 0x49, 0x2f, 0xb7, 0x3f, 0x73, 0xec, 0x4b, 0x7b, 0xa3, 0x64, 0x78, 0x8f, 0xdf,
0x8e, 0x29, 0xf9, 0x51, 0x5f, 0xb7, 0xbf, 0x3e, 0xfe, 0xc1, 0x17, 0x90, 0x7d, 0x36, 0x9b, 0xb9, 0xed, 0xbd, 0x77, 0xb5, 0x60,
0x37, 0x9d, 0x13, 0x64, 0xcc, 0xea, 0x1b, 0x8e, 0xad, 0x93, 0xb6, 0xcc, 0xeb, 0x9b, 0x36, 0xbd, 0x90, 0xb9, 0xc5, 0x66, 0xb0,
0xb9, 0x4b, 0x3a, 0xfe, 0xe5, 0xbd, 0xf6, 0x3f, 0x21, 0x67, 0x24, 0x57, 0x16, 0xd3, 0xc7, 0x57, 0xe5, 0xef, 0x4d, 0x5f, 0xaa,
0x9e, 0x79, 0xa7, 0x9f, 0x7e, 0x39, 0xe7, 0x8e, 0x42, 0x45, 0x7e, 0x34, 0x7d, 0x47, 0xdd, 0xdf, 0xa7, 0xd9, 0xe3, 0xf5, 0x4f,
0x2a, 0x3a, 0xa7, 0x59, 0xef, 0xc, 0x4, 0x76, 0x3f, 0xb3, 0x6f, 0x77, 0x5d, 0x42, 0xbb, 0x7d, 0xf, 0xb0, 0x2e, 0x69, 0xaa,
0x9a, 0x29, 0x92, 0xff, 0x0, 0x3b, 0x87, 0xaa, 0xda, 0xfb, 0x43, 0xd9, 0xa4, 0x96, 0x1e, 0x2b, 0x8a, 0xbb, 0x7b, 0x6b, 0x5c,
0xd, 0x15, 0xfc, 0xf8, 0xaf, 0x99, 0x39, 0xe6, 0x9e, 0x69, 0x90, 0x3a, 0x21, 0xb8, 0xf8, 0x41, 0xe9, 0xb5, 0xea, 0xfd, 0xd0,
0x9b, 0x17, 0x75, 0x78, 0x85, 0x8e, 0xd7, 0xba, 0x77, 0xb8, 0x21, 0xae, 0xfa, 0x9a, 0xb2, 0xfa, 0xce, 0xbb, 0x69, 0xa3, 0x5d,
0xe1, 0xb7, 0xd9, 0xed, 0xe8, 0xc9, 0x73, 0xad, 0xf7, 0x77, 0x59, 0xe1, 0xb9, 0xaf, 0x9, 0x77, 0x1e, 0x6e, 0x4e, 0x79, 0xaa,
0x4c, 0xb5, 0x8d, 0x32, 0x4f, 0x2d, 0x72, 0x57, 0x73, 0x6f, 0x7b, 0x77, 0x4d, 0x12, 0xc3, 0x28, 0x70, 0xfb, 0xd1, 0x67, 0x50,
0xd8, 0x7a, 0xf7, 0xd5, 0xbb, 0xaa, 0x74, 0x1d, 0xbb, 0x1d, 0x26, 0x23, 0x6b, 0xd1, 0xef, 0xfc, 0x82, 0xd4, 0x36, 0x7c, 0x4c,
0xd5, 0x51, 0x5c, 0xd8, 0xbd, 0x87, 0x5a, 0xea, 0x8e, 0xcb, 0xc2, 0xe6, 0xb1, 0xd2, 0xd7, 0x1d, 0x55, 0xc7, 0x54, 0x96, 0x59,
0x2b, 0x29, 0x62, 0xab, 0x9a, 0x79, 0xe6, 0x9e, 0x79, 0xa7, 0xf2, 0xe7, 0x9e, 0x1, 0xed, 0xfd, 0x41, 0xbf, 0xe2, 0x35, 0xb4,
0x7f, 0xa, 0x3a, 0xb3, 0xfe, 0x91, 0x78, 0xe, 0xdb, 0xfa, 0xd6, 0x63, 0x2e, 0x3b, 0x67, 0xd2, 0x2b, 0xaa, 0xbb, 0x13, 0x47,
0x8f, 0x8b, 0x9d, 0x5b, 0x5, 0x9a, 0xf1, 0xdf, 0xb5, 0xaf, 0x2b, 0xb1, 0xf6, 0x9e, 0xda, 0x8d, 0x2f, 0x62, 0xd2, 0x72, 0x5a,
0xbe, 0x2e, 0xe6, 0x3a, 0xed, 0x7d, 0xa1, 0xaa, 0xce, 0x9c, 0x96, 0xff, 0x0, 0x8e, 0xe7, 0x8a, 0xf8, 0xe3, 0xed, 0xfc, 0x79,
0xe3, 0x9e, 0x3d, 0xb8, 0xf6, 0xe7, 0x80, 0x84, 0x68, 0x24, 0x81, 0xa0, 0xfd, 0x47, 0xfd, 0xdf, 0xa0, 0xe8, 0x9a, 0x56, 0x89,
0x67, 0xe3, 0x7f, 0x55, 0x64, 0x2d, 0x34, 0xad, 0x4b, 0x5c, 0xd4, 0xad, 0x6f, 0xee, 0x76, 0x9d, 0xba, 0x2b, 0x9b, 0xdb, 0x6d,
0x73, 0xf, 0x67, 0x87, 0x82, 0xee, 0xe2, 0x38, 0xa9, 0xfb, 0x51, 0xcd, 0x73, 0x15, 0x9f, 0x15, 0xd7, 0x4d, 0x3f, 0xa7, 0x8a,
0xaa, 0xe7, 0x8e, 0x3f, 0x20, 0x77, 0xbb, 0xc6, 0x5f, 0x30, 0xf6, 0x5f, 0x39, 0xbd, 0x36, 0xbb, 0x9b, 0xbe, 0xb6, 0xcd, 0x3b,
0x7, 0xa2, 0xe6, 0x2f, 0xf4, 0xbe, 0xff, 0x0, 0xd5, 0xeb, 0xc0, 0xeb, 0xb7, 0xd7, 0xf9, 0x1c, 0x6d, 0x10, 0x6b, 0x7a, 0x8e,
0x4e, 0x8, 0x2e, 0xa8, 0xb9, 0xc9, 0x71, 0xc5, 0xd7, 0x32, 0xdc, 0xd3, 0x3f, 0x3c, 0xd7, 0x4f, 0x3f, 0xa7, 0x8e, 0x78, 0xfc,
0x81, 0xc3, 0xef, 0xa6, 0x4f, 0xf7, 0xef, 0xe4, 0xdf, 0xf0, 0x93, 0x53, 0xfe, 0x71, 0xa8, 0x1c, 0xa7, 0xf2, 0xb3, 0xb8, 0xbb,
0x1b, 0xa0, 0x3d, 0x50, 0x7c, 0x9c, 0xed, 0xfe, 0xa6, 0xd9, 0xaf, 0xb5, 0xd, 0xff, 0x0, 0x47, 0xf2, 0xc7, 0xba, 0x72, 0xd8,
0x1c, 0xd5, 0x85, 0x7f, 0x9d, 0x12, 0x71, 0xd8, 0x3b, 0x1c, 0x17, 0x76, 0x17, 0xd6, 0xf5, 0x7b, 0xc1, 0x91, 0xc3, 0xe5, 0xac,
0x66, 0x96, 0xd6, 0xf6, 0xd2, 0x6a, 0x6b, 0x82, 0xee, 0xd6, 0x69, 0x22, 0x92, 0x9a, 0xa8, 0xaf, 0x9e, 0x39, 0x9, 0x24, 0x66,
0xf1, 0xbe, 0x3f, 0xfd, 0x40, 0x3e, 0x16, 0xf1, 0x9c, 0xc1, 0x51, 0xaf, 0x75, 0xaf, 0x98, 0x7d, 0x3d, 0x67, 0xf6, 0x68, 0x86,
0x79, 0x2a, 0x92, 0xe7, 0x47, 0xdc, 0x2e, 0x20, 0xaa, 0x7a, 0xb0, 0x79, 0x39, 0x78, 0xa2, 0x5c, 0xc6, 0x5b, 0xa5, 0x3b, 0x32,
0xab, 0x4a, 0xea, 0xb4, 0xb9, 0xe2, 0x99, 0xa4, 0xc7, 0xdc, 0xd1, 0xcd, 0x7c, 0x71, 0x2d, 0xcd, 0x95, 0xcd, 0xbc, 0xe1, 0xfe,
0x7d, 0x3c, 0x5d, 0x61, 0xbe, 0x74, 0xbe, 0x9f, 0xe6, 0x67, 0x55, 0xf6, 0x76, 0xb5, 0x91, 0xd4, 0x37, 0xdd, 0x1b, 0xbc, 0x35,
0x7c, 0xe, 0xcd, 0xaf, 0x65, 0x22, 0xfb, 0x77, 0x56, 0x17, 0xf6, 0xda, 0x84, 0xb5, 0xd3, 0x55, 0x15, 0xd3, 0xcd, 0x50, 0xdd,
0xd8, 0xde, 0xdb, 0x49, 0x1d, 0xc5, 0xad, 0xcc, 0x35, 0x57, 0x6f, 0x77, 0x6b, 0x2c, 0x73, 0x43, 0x5d, 0x71, 0x49, 0x45, 0x75,
0x4, 0x2e, 0x73, 0xff, 0x0, 0xdf, 0xb9, 0xaf, 0xf5, 0x6c, 0x8f, 0xfb, 0xc9, 0x81, 0x2a, 0xdf, 0x40, 0x5f, 0x1c, 0x75, 0x4e,
0x94, 0xe9, 0xee, 0xeb, 0xf5, 0x22, 0xee, 0xee, 0x6d, 0xf0, 0x1a, 0xe6, 0x3f, 0x5a, 0xda, 0xb5, 0xde, 0xbf, 0xcc, 0x65, 0x21,
0xe7, 0xed, 0xe2, 0x3a, 0xfb, 0x4e, 0xa2, 0xbc, 0x9f, 0x6a, 0x6f, 0x36, 0x74, 0x55, 0x4d, 0x7f, 0x7e, 0xbc, 0xa6, 0x53, 0x13,
0x46, 0x1e, 0xce, 0xa8, 0x7e, 0x37, 0x3c, 0x55, 0x8e, 0xbe, 0x83, 0x8e, 0x2a, 0xa6, 0xe7, 0x8e, 0x2a, 0xf, 0xe9, 0xde, 0x1e,
0x5e, 0x7d, 0x3e, 0xbe, 0x48, 0xf6, 0x66, 0xc3, 0xdc, 0x3d, 0xd9, 0xd6, 0x5d, 0xcf, 0xbd, 0xf6, 0x36, 0xd3, 0xc6, 0x3a, 0x9c,
0xde, 0xc5, 0x77, 0x77, 0xe4, 0x16, 0x27, 0xf1, 0x11, 0x62, 0x31, 0x96, 0x98, 0x7c, 0x6d, 0xb5, 0xae, 0x23, 0x5e, 0xed, 0xbc,
0x46, 0xb, 0x15, 0x67, 0x67, 0x8e, 0xb1, 0x8a, 0x3a, 0x21, 0xb4, 0xb5, 0x82, 0x2e, 0x3e, 0x3c, 0xd5, 0xf1, 0xf9, 0xd5, 0x55,
0x55, 0x7, 0x4a, 0x3c, 0x1c, 0xf2, 0x83, 0xd3, 0x33, 0xc9, 0xde, 0xb9, 0xd9, 0xbc, 0x1, 0xf1, 0xa6, 0xcf, 0x6d, 0xb4, 0xeb,
0xd8, 0xba, 0xc7, 0x76, 0xa6, 0x4e, 0xb1, 0xec, 0x28, 0xf7, 0x69, 0xa9, 0xbb, 0xd1, 0x76, 0x5c, 0x8c, 0x96, 0x9b, 0x7d, 0x96,
0xbb, 0xb0, 0x76, 0x6, 0xc7, 0xb5, 0x66, 0x2e, 0xe4, 0xb5, 0xbd, 0xda, 0xf9, 0x9e, 0x8b, 0x5e, 0x2e, 0xf9, 0xae, 0xd2, 0x89,
0x39, 0x96, 0xde, 0x8a, 0x63, 0x86, 0xae, 0x63, 0x8, 0x4f, 0xf9, 0x71, 0xe3, 0x7e, 0xd9, 0xe2, 0x57, 0x91, 0x3d, 0xa5, 0xd0,
0x5b, 0x85, 0x17, 0x12, 0x5e, 0x68, 0x5b, 0x2d, 0xd5, 0xa6, 0x1b, 0x2f, 0x34, 0x15, 0x41, 0x1e, 0xcf, 0xa8, 0xde, 0xfb, 0x64,
0x35, 0x1d, 0xaa, 0xd7, 0x8e, 0x69, 0xa6, 0x3e, 0x60, 0xcf, 0xeb, 0xd7, 0x36, 0xf7, 0x15, 0x53, 0x47, 0x35, 0x71, 0xc, 0xd5,
0x57, 0x17, 0x3c, 0xfc, 0xe3, 0xab, 0x8e, 0x2, 0x52, 0x1f, 0x4c, 0x97, 0xee, 0x37, 0xc9, 0xef, 0xe2, 0xbe, 0x9d, 0xfc, 0xa1,
0x70, 0x8, 0x7b, 0x67, 0xff, 0x0, 0xbf, 0x73, 0x5f, 0xea, 0xd9, 0x1f, 0xf7, 0x93, 0x3, 0xc9, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd6, 0xaf, 0xfc, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x57, 0xe8, 0x6f, 0xdf, 0x97, 0x4c,
0xff, 0x0, 0x15, 0xfa, 0xef, 0xf9, 0xbf, 0xe, 0x9, 0x43, 0xfd, 0x4f, 0xff, 0x0, 0xdc, 0x5e, 0x17, 0x7f, 0xab, 0x77, 0xf7,
0xfb, 0x3e, 0x9d, 0x4, 0x49, 0x23, 0x92, 0x48, 0xa4, 0xa2, 0x58, 0xab, 0xae, 0x29, 0x62, 0xae, 0x99, 0x23, 0x92, 0x3a, 0xb9,
0xa2, 0x48, 0xe4, 0xa3, 0x9e, 0x2a, 0xa2, 0xba, 0x2b, 0xa7, 0x9e, 0x2a, 0xa2, 0xba, 0x2a, 0xe3, 0x8e, 0x78, 0xe7, 0x8e, 0x7d,
0xf8, 0xe4, 0x13, 0x4d, 0xf4, 0x6b, 0xf5, 0x44, 0xc0, 0x79, 0x59, 0xaf, 0xea, 0xfe, 0x28, 0x79, 0x49, 0x79, 0x8d, 0xcc, 0x77,
0xe6, 0x85, 0x5d, 0x9e, 0x67, 0xaa, 0x37, 0x3d, 0xa2, 0x3b, 0x4b, 0x89, 0x7b, 0x52, 0xcb, 0x51, 0xa7, 0x9c, 0x8e, 0x36, 0x5e,
0x6e, 0x6e, 0xb8, 0xe7, 0x9e, 0x3b, 0x6b, 0x4a, 0xb3, 0xb7, 0xaa, 0x4e, 0x67, 0xa3, 0xe3, 0x73, 0x93, 0xc7, 0xc3, 0x55, 0xcf,
0x3c, 0xd7, 0x3c, 0x77, 0x92, 0x48, 0x1c, 0xdb, 0xfa, 0x96, 0xbf, 0xe7, 0x33, 0xa5, 0xff, 0x0, 0xfa, 0xc7, 0xaf, 0xff, 0x0,
0xdd, 0x5e, 0xd8, 0x7, 0x49, 0xfd, 0x3d, 0xbc, 0x99, 0xe8, 0xbf, 0x55, 0x8f, 0x8, 0x72, 0x5e, 0x8, 0x79, 0x29, 0x99, 0x8f,
0x8e, 0xea, 0xd6, 0xf4, 0x5b, 0x6d, 0x52, 0xfe, 0x3b, 0xc9, 0xac, 0x6d, 0x76, 0x4d, 0xab, 0x9, 0xab, 0x53, 0x4f, 0xf4, 0x1b,
0xb7, 0x7a, 0xfe, 0xe6, 0xf3, 0xef, 0x71, 0x91, 0xdb, 0xb4, 0xf8, 0xac, 0x2d, 0x2a, 0xca, 0xd3, 0xf0, 0x92, 0x4e, 0x2e, 0xe0,
0xaa, 0x6b, 0x88, 0xeb, 0xb4, 0xbc, 0xaa, 0x9e, 0x42, 0x39, 0xde, 0x64, 0xfa, 0x56, 0xf9, 0x75, 0xe1, 0xb6, 0xd1, 0x99, 0x83,
0x64, 0xeb, 0xbc, 0xf7, 0x62, 0xf5, 0x85, 0xbd, 0xc5, 0xd4, 0xb8, 0xe, 0xe6, 0xeb, 0xac, 0x16, 0x53, 0x3f, 0xa7, 0x64, 0xb0,
0xd1, 0xd7, 0x5d, 0x56, 0xb7, 0x5b, 0x1c, 0x58, 0xf8, 0xaf, 0xaf, 0x74, 0x1c, 0xbf, 0x36, 0xdc, 0x71, 0xf8, 0x8b, 0x2c, 0xa7,
0xdb, 0xa6, 0x99, 0xa9, 0x93, 0x8b, 0x69, 0xee, 0xe0, 0xa6, 0x9b, 0x8a, 0xc3, 0x4d, 0xba, 0x9b, 0xa1, 0xfb, 0xab, 0xbe, 0x33,
0xd6, 0xfa, 0xcf, 0x4c, 0x75, 0x56, 0xfd, 0xd9, 0xd9, 0xab, 0x8b, 0xb8, 0x2c, 0xbf, 0xb, 0xa5, 0xea, 0xf9, 0x7c, 0xf4, 0x76,
0x93, 0x4f, 0xf9, 0xd3, 0x5e, 0x56, 0xfa, 0xc6, 0xd6, 0x5b, 0xc, 0x35, 0xa4, 0x71, 0xfb, 0xc9, 0x2d, 0xc5, 0xdc, 0xb0, 0x41,
0xc, 0x54, 0xd5, 0x24, 0x95, 0xd3, 0x45, 0x3c, 0xd5, 0xc0, 0x4c, 0x2b, 0xd3, 0x9f, 0xc0, 0xde, 0xb7, 0xf4, 0x90, 0xea, 0xd,
0xff, 0x0, 0xcc, 0x8f, 0x34, 0xf7, 0x4d, 0x5b, 0x1, 0xda, 0x97, 0x9a, 0xa4, 0xf8, 0xab, 0xda, 0x22, 0xc8, 0xdb, 0xe4, 0xf0,
0xfd, 0x65, 0xab, 0x5c, 0xcb, 0x16, 0x47, 0x9d, 0x1f, 0x57, 0x9a, 0xf, 0x7e, 0x77, 0x4e, 0xcf, 0xdc, 0xef, 0xb1, 0xd0, 0x45,
0x37, 0xec, 0xfa, 0x67, 0xf9, 0xd7, 0x15, 0x16, 0x56, 0x1c, 0xcb, 0x1d, 0x57, 0x13, 0xdd, 0x84, 0x55, 0xfc, 0xe9, 0xf2, 0xcb,
0x63, 0xf3, 0x5b, 0xc9, 0xbe, 0xc6, 0xef, 0xdc, 0xed, 0xad, 0xc6, 0x27, 0x1d, 0xb0, 0x5e, 0xc1, 0x89, 0xd1, 0xf5, 0x9b, 0x89,
0xa8, 0x9e, 0xbd, 0x4f, 0xaf, 0xf0, 0x31, 0xf3, 0x63, 0xab, 0x60, 0xab, 0x92, 0x2f, 0x78, 0x2b, 0xbe, 0xa6, 0xd3, 0x8a, 0xae,
0xaf, 0xab, 0x8f, 0xfa, 0xb9, 0x72, 0x37, 0x57, 0x12, 0x51, 0xc7, 0x14, 0xd7, 0xc7, 0x1c, 0x6, 0xa2, 0x3, 0xb0, 0x9e, 0x93,
0x1e, 0x9a, 0x3a, 0x7, 0xa8, 0x66, 0xc5, 0xda, 0xb0, 0xf6, 0x7, 0x74, 0x73, 0xa0, 0xe3, 0x7a, 0xeb, 0x1, 0x1d, 0x76, 0xba,
0x7e, 0xa1, 0x5e, 0x3a, 0x7e, 0xcb, 0xcc, 0x65, 0x33, 0x50, 0xcd, 0x6f, 0x88, 0xda, 0x79, 0xb3, 0xce, 0x63, 0xee, 0xf1, 0x74,
0x68, 0x38, 0x2c, 0x97, 0x11, 0xd3, 0x7d, 0x54, 0x7c, 0x49, 0x73, 0x75, 0x35, 0x74, 0xdb, 0x71, 0x5d, 0x9f, 0x32, 0xd1, 0x72,
0xd, 0x27, 0xf2, 0x9f, 0xc2, 0xdf, 0x21, 0xbc, 0x3e, 0xec, 0x9c, 0x97, 0x5c, 0xf7, 0x1f, 0x5f, 0x67, 0x71, 0x9c, 0xc7, 0x97,
0xab, 0x19, 0xab, 0xee, 0x36, 0x38, 0xcb, 0xeb, 0xdd, 0x23, 0x7f, 0xb7, 0x9e, 0xb9, 0xf9, 0xc5, 0x64, 0x74, 0xdd, 0x8e, 0x2b,
0x7e, 0x6c, 0x32, 0xbc, 0x65, 0x2d, 0xa0, 0xe6, 0x4e, 0x2d, 0x3e, 0x54, 0xdf, 0xdb, 0x55, 0xc5, 0x51, 0x5c, 0x43, 0x14, 0xd1,
0xd7, 0x1d, 0x21, 0x28, 0x7f, 0xa7, 0x97, 0xc4, 0x9e, 0xe8, 0xe8, 0x1d, 0x1f, 0xbd, 0x3b, 0xf7, 0xba, 0x30, 0x39, 0x8e, 0xb1,
0xd6, 0x3b, 0x6b, 0x1b, 0xa5, 0x63, 0xb4, 0xad, 0x67, 0x6f, 0x82, 0xe7, 0x5f, 0xcb, 0xe4, 0xf0, 0x1a, 0x7f, 0x3b, 0x1e, 0x63,
0x21, 0xd8, 0x39, 0xac, 0x3e, 0x4e, 0x9b, 0x59, 0x70, 0xf8, 0x19, 0x28, 0xce, 0xd1, 0x16, 0x2a, 0x6b, 0xaa, 0x68, 0x92, 0xe6,
0xf, 0xc4, 0xdc, 0x53, 0x4d, 0x36, 0xb5, 0xdb, 0xcd, 0x70, 0x1c, 0xdf, 0xf4, 0xf6, 0xec, 0x8d, 0x67, 0xb8, 0x3d, 0x7d, 0xb2,
0xbd, 0xa3, 0xa5, 0xd7, 0x44, 0xda, 0x86, 0xf9, 0xdb, 0x5e, 0x58, 0xec, 0xda, 0xc5, 0xdd, 0x11, 0xd3, 0x17, 0x19, 0x1c, 0xe,
0x53, 0x40, 0xed, 0x5b, 0x8c, 0x4e, 0x5a, 0xa8, 0xe9, 0xfc, 0xa8, 0x97, 0x2f, 0x63, 0x55, 0x17, 0x55, 0xf1, 0xef, 0xcf, 0x3c,
0x57, 0x2f, 0x3e, 0xfc, 0xf3, 0xcf, 0xe7, 0xc8, 0x62, 0x7f, 0xa8, 0x37, 0xfc, 0x46, 0xb6, 0x8f, 0xe1, 0x47, 0x56, 0x7f, 0xd2,
0x2f, 0x1, 0xd5, 0x6f, 0x45, 0xff, 0x0, 0x33, 0xfa, 0x77, 0xca, 0x9f, 0x18, 0x72, 0x3e, 0x9b, 0x5e, 0x4b, 0xcb, 0x8a, 0xbd,
0xd8, 0xf1, 0xba, 0x96, 0x7f, 0x47, 0xd3, 0x31, 0x5b, 0x15, 0xcc, 0x36, 0x90, 0x76, 0xb7, 0x51, 0x64, 0x6d, 0xee, 0xe6, 0xa3,
0x5d, 0xc2, 0x5d, 0xfd, 0xc8, 0x25, 0xa3, 0x77, 0xeb, 0xab, 0x69, 0x64, 0xa2, 0xde, 0x3b, 0x7e, 0x63, 0xbc, 0x8f, 0x1b, 0x6d,
0x6d, 0x77, 0x6d, 0x55, 0x52, 0xda, 0x5c, 0xcb, 0x10, 0x71, 0x87, 0xce, 0xff, 0x0, 0x47, 0x6f, 0x28, 0xfc, 0x41, 0xdc, 0x73,
0x39, 0xd, 0x3f, 0x4a, 0xda, 0xfb, 0xbb, 0xa2, 0x2e, 0xb2, 0x37, 0x32, 0x6a, 0x7d, 0x91, 0xa2, 0xe0, 0xef, 0x36, 0x5c, 0xa6,
0x2b, 0x15, 0x24, 0xde, 0xf6, 0x78, 0xfe, 0xcb, 0xd7, 0xb0, 0x36, 0x93, 0xe4, 0x35, 0x5c, 0xc5, 0xa5, 0x12, 0x47, 0xc, 0x97,
0xb5, 0x41, 0x4e, 0x22, 0xf2, 0x5a, 0xa9, 0xfc, 0x3c, 0xfc, 0x49, 0x5d, 0x56, 0xd1, 0x7, 0x34, 0x3a, 0xef, 0xa8, 0xbb, 0x53,
0xb7, 0x36, 0x28, 0xf5, 0x1e, 0xad, 0xeb, 0x8d, 0xe3, 0xb0, 0xf6, 0x79, 0x2e, 0xa0, 0xb2, 0xfd, 0x85, 0xa6, 0x6a, 0xf9, 0x9d,
0x8f, 0x25, 0xd, 0xc5, 0xcd, 0x72, 0xc7, 0xd, 0x17, 0x76, 0xb8, 0xab, 0x3b, 0x99, 0x2c, 0xa9, 0xe6, 0xab, 0x79, 0x39, 0xe6,
0xb9, 0xb8, 0xa2, 0x8a, 0x69, 0x8e, 0xba, 0xaa, 0xe7, 0x8e, 0x28, 0xab, 0x9e, 0x2, 0x72, 0x5e, 0x13, 0xf4, 0x7, 0x60, 0x78,
0x5b, 0xe9, 0x1b, 0xd9, 0xfa, 0x3f, 0x91, 0x7c, 0x6b, 0xba, 0x6, 0xcd, 0xfd, 0x2, 0xef, 0xad, 0xcf, 0x33, 0x63, 0x79, 0xb1,
0xe3, 0x24, 0xb6, 0xd5, 0xac, 0xf7, 0xd, 0x72, 0xfe, 0x3c, 0x3e, 0x2b, 0x61, 0xcc, 0xf1, 0x2d, 0x18, 0x3b, 0x7c, 0xdd, 0x55,
0xf1, 0x45, 0x32, 0x45, 0x5, 0xcd, 0xc4, 0x34, 0xc9, 0x35, 0x11, 0xf1, 0x2d, 0x52, 0x73, 0x55, 0x14, 0x87, 0x25, 0xbe, 0x99,
0x3f, 0xdf, 0xbf, 0x93, 0x7f, 0xc2, 0x4d, 0x4f, 0xf9, 0xc6, 0xa0, 0x71, 0x6b, 0xd4, 0x47, 0xfe, 0x7d, 0x7c, 0xcb, 0xff, 0x0,
0xec, 0xe7, 0x76, 0xff, 0x0, 0xdc, 0x4d, 0x80, 0x1f, 0x19, 0xe2, 0x67, 0x95, 0x9d, 0xb1, 0xe1, 0xaf, 0x75, 0xeb, 0x1d, 0xdd,
0xd4, 0x39, 0x6f, 0xc2, 0x66, 0x70, 0xb3, 0x53, 0x69, 0x9e, 0xc0, 0x5d, 0xd7, 0x2f, 0x3a, 0xfe, 0xf3, 0xaa, 0x5c, 0x5c, 0x5b,
0xcb, 0x9a, 0xd3, 0x76, 0x7b, 0x48, 0xf9, 0xe3, 0x9b, 0x9c, 0x3e, 0x5e, 0x2b, 0x7a, 0x78, 0xf9, 0xd3, 0xed, 0x3d, 0xac, 0xf4,
0x47, 0x71, 0x5, 0x71, 0xcf, 0x14, 0x72, 0x52, 0x16, 0x1f, 0xf8, 0x61, 0xe4, 0xe7, 0x45, 0xf9, 0x95, 0xd5, 0x16, 0x9e, 0x45,
0xf4, 0xcc, 0x18, 0xfb, 0x4b, 0xed, 0xba, 0x2c, 0x6e, 0xf, 0xb1, 0xf1, 0x72, 0x45, 0x63, 0x1e, 0xe3, 0xac, 0x6d, 0x9a, 0xd5,
0xa5, 0x5e, 0xfa, 0x7e, 0xeb, 0x25, 0xad, 0x34, 0xcb, 0x75, 0x79, 0x81, 0x87, 0x2b, 0xcd, 0x56, 0x53, 0xd7, 0xfd, 0x5d, 0xcd,
0x85, 0xcc, 0x53, 0xc3, 0xfd, 0x54, 0xb4, 0x71, 0xc0, 0x57, 0x61, 0xd1, 0x7d, 0xf, 0xb8, 0x79, 0x3b, 0xe4, 0x9e, 0x91, 0xd0,
0xba, 0x24, 0x5c, 0xd5, 0xb1, 0xf6, 0x67, 0x60, 0xd5, 0xaf, 0xc3, 0x79, 0xcc, 0x35, 0xdc, 0x41, 0x84, 0xc5, 0xf3, 0x79, 0x73,
0x79, 0xb1, 0x6c, 0xd7, 0xd1, 0x47, 0xcd, 0x32, 0x57, 0x8c, 0xd5, 0xf5, 0xeb, 0x4b, 0xac, 0x8d, 0xcf, 0x14, 0xf3, 0xf2, 0xe6,
0xde, 0xda, 0xbf, 0x8f, 0xbd, 0x5e, 0xdc, 0x72, 0x12, 0x62, 0xf5, 0xd8, 0xee, 0xfd, 0x4f, 0xc5, 0x6f, 0x15, 0xfa, 0x1b, 0xd3,
0x87, 0xa3, 0xe7, 0x8f, 0xb, 0x8e, 0xcc, 0xea, 0xf8, 0x1b, 0xad, 0xc3, 0x1f, 0x6b, 0x73, 0x4f, 0x19, 0x3b, 0xe, 0xa3, 0xeb,
0xf9, 0xad, 0xec, 0x75, 0x4b, 0x1c, 0xbd, 0x50, 0xf1, 0xd, 0x53, 0xdd, 0x76, 0x1e, 0xe9, 0x8d, 0x9a, 0xfa, 0xea, 0xe3, 0x9e,
0x3e, 0x77, 0x32, 0x61, 0xae, 0x3e, 0xef, 0x1c, 0xf1, 0x71, 0x57, 0x35, 0x4, 0x46, 0x1, 0xb0, 0xbe, 0x28, 0xf9, 0x9, 0xb2,
0xf8, 0xab, 0xe4, 0x57, 0x52, 0xf7, 0xfe, 0xab, 0xf7, 0xe5, 0xc8, 0x75, 0xbe, 0xdd, 0x61, 0x97, 0xbf, 0xc6, 0xdb, 0xcf, 0xcd,
0xbf, 0x39, 0xfd, 0x62, 0xe3, 0x89, 0x31, 0x9b, 0x7e, 0xb1, 0x24, 0xbe, 0xfc, 0x71, 0x44, 0x3b, 0x2e, 0xaf, 0x7d, 0x77, 0x63,
0x55, 0x5c, 0xfb, 0xf1, 0x47, 0x13, 0xfc, 0xbf, 0xb6, 0x9e, 0x1, 0x28, 0x5f, 0x5e, 0x8f, 0x1c, 0x75, 0x5f, 0x26, 0x3c, 0x62,
0xea, 0xf, 0x50, 0xbe, 0x93, 0xe2, 0x1d, 0x86, 0x8d, 0x4f, 0x59, 0xd7, 0x78, 0xd9, 0xb2, 0xd8, 0xc8, 0x38, 0xae, 0x6d, 0x8f,
0xa3, 0x7b, 0x2, 0xb8, 0x32, 0x7a, 0xb6, 0x7a, 0xe2, 0x98, 0x63, 0x9a, 0xe2, 0xa9, 0x74, 0x7d, 0x9b, 0x35, 0x4f, 0xce, 0x2f,
0xd3, 0xf8, 0x7b, 0x6c, 0xc5, 0xdc, 0x93, 0x73, 0xc7, 0x16, 0xde, 0xd4, 0x87, 0xab, 0xf4, 0xc9, 0x7e, 0xe3, 0x7c, 0x9e, 0xfe,
0x2b, 0xe9, 0xdf, 0xca, 0x17, 0x0, 0x8d, 0x2e, 0x6f, 0xc0, 0xcf, 0x39, 0x65, 0xcc, 0xe5, 0xe5, 0x8b, 0xc3, 0x1f, 0x2b, 0xe4,
0x8a, 0x4c, 0x9d, 0xfc, 0x91, 0xc9, 0x1f, 0x8e, 0xbd, 0xbf, 0x5c, 0x72, 0x47, 0x5d, 0xd4, 0xb5, 0x51, 0x5d, 0x15, 0xd3, 0xa7,
0xf3, 0x4d, 0x74, 0x57, 0x4f, 0x3c, 0x73, 0xc7, 0x3c, 0x73, 0xed, 0xcf, 0x0, 0xc0, 0x7d, 0x99, 0xd3, 0x5d, 0xbf, 0xd2, 0xb9,
0x5c, 0x7e, 0xb, 0xb9, 0x3a, 0xa7, 0xb2, 0x7a, 0x97, 0x39, 0x96, 0xc7, 0xfe, 0xd6, 0xc5, 0xe1, 0xbb, 0x33, 0x46, 0xd9, 0xf4,
0x3c, 0xae, 0x4b, 0x15, 0xf8, 0x99, 0xac, 0xff, 0x0, 0x69, 0xe3, 0xf1, 0xdb, 0x4e, 0x2f, 0x15, 0x77, 0x79, 0x8f, 0xfc, 0x5d,
0xb4, 0x91, 0x7d, 0xe8, 0xe8, 0xaa, 0x3f, 0xb9, 0x1d, 0x54, 0xfc, 0xbe, 0x54, 0xf3, 0xc7, 0x1, 0x8d, 0x80, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xd7, 0xaf, 0xfc, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xb9, 0xac, 0x6c, 0x39, 0x1d,
0x4b, 0x64, 0xd7, 0xb6, 0xbc, 0x47, 0x30, 0xd3, 0x96, 0xd6, 0x73, 0x98, 0x9d, 0x87, 0x17, 0x55, 0xc4, 0x5f, 0x7e, 0xde, 0x9c,
0x8e, 0x16, 0xfe, 0xdf, 0x25, 0x65, 0xcc, 0xf0, 0xfc, 0xa9, 0xfb, 0xd0, 0xf1, 0x73, 0x6d, 0x4f, 0xca, 0x9f, 0x7e, 0x3e, 0x54,
0xfb, 0xf1, 0xee, 0xd, 0xc8, 0xf3, 0x1f, 0xd4, 0x3b, 0xc9, 0x1f, 0x3b, 0x2d, 0xba, 0xfa, 0xd7, 0xbf, 0xb2, 0xba, 0x96, 0x4a,
0x1e, 0xb2, 0x9f, 0x67, 0xb8, 0xd5, 0xb8, 0xd6, 0x35, 0x4b, 0x3d, 0x6b, 0x98, 0x64, 0xdb, 0xa3, 0xc0, 0x47, 0x99, 0xe6, 0xf6,
0xab, 0x59, 0xa5, 0xe6, 0xf3, 0x8a, 0xe9, 0xd6, 0xed, 0x7e, 0xdf, 0x15, 0x7b, 0x7d, 0xbf, 0x6a, 0xbd, 0xbf, 0xf7, 0x7e, 0x41,
0xa3, 0x60, 0xf5, 0xf5, 0xfd, 0x83, 0x39, 0xa9, 0xe7, 0xb0, 0xbb, 0x46, 0xb1, 0x97, 0xc9, 0x6b, 0xfb, 0x26, 0xb9, 0x95, 0xc7,
0xe7, 0x70, 0x19, 0xec, 0x35, 0xe5, 0xc6, 0x3b, 0x2d, 0x85, 0xcc, 0xe2, 0x6e, 0xe2, 0xbe, 0xc6, 0x65, 0x71, 0x97, 0xf6, 0xb2,
0x45, 0x73, 0x65, 0x90, 0xc7, 0xde, 0xc1, 0x44, 0xb0, 0xcb, 0x1d, 0x54, 0xd7, 0x1c, 0x94, 0x71, 0x55, 0x3c, 0xf1, 0xcf, 0x1c,
0x72, 0xd, 0xb6, 0xf3, 0x53, 0xcd, 0xce, 0xcc, 0xf3, 0x9f, 0x6d, 0xea, 0xfd, 0xf7, 0xb6, 0xb1, 0x58, 0x1b, 0x3d, 0xcb, 0xae,
0xfa, 0x87, 0x3, 0xd5, 0x37, 0xf9, 0x9c, 0x15, 0x13, 0x5b, 0xd3, 0xb9, 0x57, 0x84, 0xce, 0x6c, 0x79, 0xd9, 0x76, 0xdc, 0x9d,
0x85, 0x7c, 0xfe, 0x13, 0x1b, 0x97, 0xcb, 0x5d, 0x6c, 0x72, 0x73, 0x3c, 0x16, 0xbc, 0x51, 0x6b, 0x4d, 0x54, 0x7b, 0xc7, 0x45,
0x14, 0xf3, 0xc5, 0x14, 0x86, 0xa6, 0x6b, 0xbb, 0x26, 0xc3, 0xa8, 0x67, 0x71, 0x5b, 0x46, 0xa5, 0x9e, 0xcd, 0x6a, 0xfb, 0x36,
0xa, 0xf6, 0xc, 0x9e, 0xf, 0x62, 0xd7, 0x72, 0x97, 0xd8, 0x4c, 0xee, 0x1b, 0x25, 0x6b, 0x5f, 0xce, 0xdb, 0x21, 0x8a, 0xcb,
0xe3, 0x67, 0xb6, 0xc8, 0x63, 0xaf, 0x6d, 0xeb, 0xe3, 0xde, 0x89, 0x61, 0x92, 0x89, 0x28, 0xe7, 0xf3, 0xe3, 0x9e, 0x1, 0xdc,
0xce, 0x8e, 0xfa, 0x87, 0x7c, 0xe4, 0xea, 0xfc, 0x3e, 0x33, 0x5d, 0xec, 0x3c, 0x6f, 0x57, 0x77, 0xd6, 0x3b, 0x1d, 0x6f, 0xcd,
0xaf, 0xed, 0xcd, 0xd7, 0x3, 0x92, 0xd7, 0xf7, 0xdb, 0x88, 0x61, 0x86, 0x58, 0xec, 0x68, 0x9f, 0x63, 0xd3, 0x32, 0xb8, 0x6c,
0x25, 0xe5, 0x70, 0x7b, 0xc7, 0xc4, 0xb3, 0xdd, 0x62, 0x2e, 0x6f, 0x2e, 0xa9, 0x8f, 0xde, 0x59, 0xb9, 0x9a, 0xba, 0xe6, 0xa8,
0x33, 0x2e, 0xd3, 0xf5, 0x30, 0x79, 0x3f, 0x7f, 0x87, 0x9e, 0xd7, 0x50, 0xe8, 0x2e, 0x8d, 0xd6, 0xf3, 0x52, 0xf3, 0xf0, 0x8b,
0x2f, 0x99, 0xba, 0xde, 0x76, 0x9b, 0x6b, 0x58, 0xaa, 0xa2, 0x4a, 0x64, 0xae, 0x1c, 0x4d, 0xbe, 0x7b, 0x5a, 0xf9, 0xde, 0x53,
0x55, 0x54, 0xd5, 0x15, 0x72, 0x4f, 0x5c, 0x54, 0xd5, 0x4f, 0xeb, 0x8a, 0x4e, 0x39, 0xf6, 0xe0, 0x38, 0xad, 0xe4, 0xff, 0x0,
0x99, 0xde, 0x4a, 0xf9, 0x8b, 0xb4, 0xd1, 0xb4, 0xf9, 0x3, 0xda, 0x59, 0xdd, 0xd7, 0xf0, 0x53, 0x4d, 0x2e, 0xbf, 0xac, 0x51,
0xcc, 0x38, 0x6d, 0x17, 0x53, 0xa2, 0x6f, 0x78, 0xfe, 0xde, 0xb1, 0xa5, 0xe1, 0xe2, 0xb3, 0xd7, 0xf1, 0x73, 0x7e, 0x1b, 0xe3,
0xc, 0xb7, 0x9c, 0x41, 0x5e, 0x42, 0xf2, 0x88, 0xe8, 0xe6, 0xea, 0xe2, 0x7a, 0xf8, 0xf9, 0xf2, 0x1a, 0xba, 0x0, 0x32, 0x7f,
0x4e, 0x77, 0x4f, 0x6a, 0x78, 0xfb, 0xd8, 0x38, 0x3e, 0xd4, 0xe9, 0x8d, 0xe3, 0x39, 0xd7, 0xbb, 0xfe, 0xb9, 0x24, 0x95, 0xe2,
0xb6, 0x2c, 0xc, 0xf1, 0xd1, 0x3d, 0x31, 0x4f, 0x4f, 0xdb, 0xba, 0xb0, 0xbe, 0xb3, 0xba, 0x8a, 0xe7, 0x1b, 0x98, 0xc4, 0x5f,
0xc5, 0xfa, 0x2e, 0x6c, 0xaf, 0x21, 0x9e, 0xd2, 0xe6, 0x3f, 0xd3, 0x2c, 0x75, 0xd3, 0xf9, 0x3, 0xbe, 0x5a, 0x57, 0xd4, 0xb5,
0xe5, 0x3e, 0x23, 0x3, 0x6d, 0x8f, 0xde, 0x3a, 0x3b, 0xa4, 0x77, 0x4c, 0xe5, 0xb7, 0xc2, 0x2a, 0xb6, 0x1c, 0x6c, 0x9b, 0x96,
0xa1, 0xcd, 0xfc, 0x11, 0xdb, 0x5b, 0xc5, 0xc4, 0xd9, 0x1c, 0x4d, 0x19, 0xbc, 0xed, 0x9f, 0x39, 0x49, 0xee, 0x28, 0x96, 0x59,
0x64, 0xb5, 0xe6, 0xd6, 0xdb, 0x9f, 0xb9, 0xc5, 0x31, 0xdb, 0xc5, 0xc5, 0x3f, 0xa8, 0x34, 0xf3, 0xcc, 0x7f, 0x5a, 0xff, 0x0,
0x31, 0x7c, 0xbe, 0xd5, 0x33, 0x3d, 0x69, 0x35, 0xe6, 0xb1, 0xd3, 0x1d, 0x53, 0x9f, 0xb5, 0xe7, 0x1f, 0x9f, 0xd4, 0x3a, 0xb6,
0xdf, 0x29, 0x67, 0x94, 0xda, 0xb1, 0x72, 0x53, 0xed, 0x73, 0x8c, 0xdb, 0x37, 0x2c, 0xbe, 0x4b, 0x21, 0x9c, 0xbe, 0xc6, 0x5e,
0x7c, 0xab, 0x8e, 0xe2, 0xd2, 0xc7, 0xf6, 0x6d, 0x95, 0xdd, 0xb5, 0x5f, 0x66, 0xe6, 0x9, 0xe9, 0xf9, 0xf3, 0x58, 0x73, 0xdb,
0xc6, 0xcf, 0x22, 0xfb, 0x2f, 0xc5, 0x1e, 0xe3, 0xd5, 0xbb, 0xd7, 0xa8, 0x6e, 0xf1, 0x16, 0x5b, 0xfe, 0x9f, 0x6, 0x7e, 0xdf,
0xb, 0x73, 0x9d, 0xc4, 0xc3, 0x9c, 0xc5, 0xd1, 0x1e, 0xcb, 0xae, 0xe5, 0x35, 0x7c, 0xa7, 0x17, 0x18, 0xcb, 0x8a, 0xe3, 0x8a,
0x7e, 0x6b, 0xc5, 0x66, 0x26, 0xe2, 0x8e, 0x79, 0xe7, 0x8f, 0x85, 0x7c, 0xf1, 0x57, 0xf9, 0x3, 0xd6, 0xf2, 0x93, 0xca, 0x4e,
0xd7, 0xf3, 0xb, 0xb5, 0xee, 0xfb, 0xa3, 0xba, 0x2e, 0xf0, 0x77, 0xdb, 0xc5, 0xf6, 0xf, 0xb, 0xaf, 0x5c, 0x5c, 0x6b, 0xd8,
0x58, 0x70, 0x38, 0xea, 0xf1, 0xd8, 0x18, 0x65, 0x83, 0x1d, 0xc7, 0x18, 0xe8, 0x25, 0x96, 0x2a, 0x26, 0xa2, 0x29, 0x79, 0xe2,
0xaa, 0xb8, 0xe7, 0x8f, 0x97, 0xb7, 0x1f, 0x97, 0xbf, 0xf6, 0x86, 0x0, 0xc7, 0x64, 0x72, 0x18, 0x7c, 0x85, 0x86, 0x5f, 0x11,
0x7f, 0x79, 0x8b, 0xca, 0xe2, 0xef, 0x2d, 0x72, 0x38, 0xcc, 0x9e, 0x3a, 0xea, 0x7b, 0x1c, 0x86, 0x3b, 0x21, 0x63, 0x3c, 0x77,
0x36, 0x57, 0xf6, 0x17, 0xb6, 0xd2, 0x45, 0x73, 0x67, 0x79, 0x67, 0x73, 0x15, 0x32, 0x45, 0x2c, 0x75, 0x53, 0x5c, 0x75, 0xd3,
0xc5, 0x54, 0xf3, 0xc7, 0x3c, 0x71, 0xcf, 0x1, 0xdb, 0xbf, 0x1e, 0xbe, 0xa0, 0x2f, 0x3a, 0x7a, 0x5f, 0x13, 0x8e, 0xd6, 0x77,
0x8b, 0x8d, 0x17, 0xc8, 0x4d, 0x7e, 0xc2, 0x3a, 0x2d, 0xe3, 0xbd, 0xed, 0xc, 0x56, 0x52, 0x1d, 0xf6, 0x3b, 0x48, 0x21, 0xb9,
0xa6, 0x18, 0xa3, 0xde, 0x35, 0x8c, 0xb6, 0x1a, 0x5c, 0x94, 0xf5, 0x5c, 0x4b, 0x1d, 0x53, 0x5c, 0xe5, 0xec, 0xf2, 0xd7, 0x52,
0xd1, 0x17, 0xc7, 0xee, 0x53, 0x55, 0x5c, 0xd7, 0xc0, 0x6c, 0x9e, 0x67, 0xea, 0x67, 0xf2, 0x42, 0x7c, 0x65, 0xdc, 0x5a, 0xff,
0x0, 0x8e, 0x7d, 0x23, 0x8c, 0xcc, 0x57, 0x45, 0x1c, 0x58, 0xdf, 0xe6, 0x72, 0xdb, 0xe6, 0x77, 0x19, 0x6f, 0x27, 0x12, 0xd1,
0xcc, 0x95, 0x5d, 0xe2, 0x6c, 0xb3, 0x1a, 0xed, 0xd5, 0xe5, 0x15, 0x43, 0xc5, 0x54, 0xf1, 0x4d, 0x17, 0xb0, 0x73, 0x4d, 0x5c,
0xf1, 0x57, 0xbf, 0x3c, 0x71, 0xcd, 0x35, 0x7, 0x1f, 0x7c, 0xb3, 0xf5, 0xc, 0xf2, 0xcb, 0xcd, 0x5b, 0xc8, 0x69, 0xef, 0x4e,
0xcf, 0xbe, 0xc8, 0xea, 0x96, 0x17, 0x3c, 0x5e, 0x61, 0xfa, 0xdb, 0x59, 0xb6, 0x8b, 0x55, 0xeb, 0x9c, 0x4d, 0xcd, 0x14, 0xf1,
0xc4, 0x77, 0x54, 0x6b, 0x58, 0xce, 0x68, 0xa3, 0x33, 0x91, 0x83, 0x9e, 0x6b, 0xe6, 0x2b, 0xdc, 0xa4, 0xb7, 0xf7, 0xd0, 0xd3,
0x2d, 0x74, 0x47, 0x35, 0x31, 0xd5, 0xf0, 0xe0, 0x3c, 0x9f, 0xf, 0xbc, 0xe5, 0xef, 0xbf, 0x6, 0xb6, 0x4d, 0xc7, 0x6b, 0xe8,
0x4c, 0x86, 0xb1, 0x8e, 0xcc, 0x6f, 0x58, 0x3b, 0xd, 0x7b, 0x3d, 0x2e, 0xcd, 0xad, 0xdb, 0x6c, 0x90, 0xd7, 0x8d, 0xc7, 0x5f,
0xd5, 0x92, 0xb7, 0x8e, 0xd6, 0xb, 0x99, 0xa2, 0xa2, 0xda, 0x5f, 0xc5, 0x55, 0xef, 0x55, 0x7c, 0x7b, 0xf3, 0xcf, 0x1c, 0x7b,
0x7e, 0x5f, 0x9f, 0xb8, 0x6b, 0xdf, 0x6a, 0xf6, 0x56, 0xd1, 0xdc, 0x9d, 0x99, 0xbf, 0xf6, 0xd6, 0xed, 0x35, 0x9d, 0xc6, 0xe1,
0xd9, 0x7b, 0x8e, 0xc5, 0xbd, 0x6d, 0x13, 0xe3, 0xec, 0xe8, 0xc7, 0xd8, 0x4d, 0x9f, 0xda, 0x32, 0xb7, 0x59, 0x9c, 0xac, 0x96,
0x76, 0x31, 0x73, 0x54, 0x76, 0x76, 0xb5, 0xde, 0xde, 0x57, 0xcd, 0x11, 0x53, 0xcf, 0x3c, 0x51, 0x4f, 0xb7, 0x1f, 0xe4, 0xf,
0x81, 0x6, 0xed, 0xf8, 0x2b, 0xe7, 0x97, 0x73, 0x78, 0xd, 0xda, 0xb7, 0x3d, 0x8b, 0xd5, 0x92, 0xda, 0xe6, 0xb0, 0xbb, 0x6,
0x3b, 0x9c, 0x46, 0xf9, 0xd7, 0x19, 0xeb, 0x9b, 0xd8, 0xf5, 0x2d, 0xe3, 0x1f, 0xc, 0x77, 0x5c, 0xe2, 0x6b, 0xc9, 0xc5, 0x67,
0x25, 0x13, 0x5a, 0xe5, 0xf5, 0xfb, 0xdb, 0xaa, 0xa7, 0xb0, 0xbd, 0x8b, 0xda, 0x7b, 0x7e, 0x6b, 0x96, 0x2f, 0x7a, 0xa0, 0x9e,
0x78, 0xe4, 0xf, 0x94, 0xf1, 0x5f, 0xcc, 0xbe, 0xe1, 0xf0, 0xdf, 0xb3, 0x76, 0x1e, 0xdd, 0xe9, 0x58, 0xb4, 0xbb, 0x5d, 0xdf,
0x62, 0xc1, 0x64, 0x75, 0xb9, 0x32, 0x7b, 0x5e, 0xab, 0x67, 0xb5, 0x7e, 0xcb, 0xc4, 0xe5, 0xb2, 0x76, 0x79, 0x5c, 0x84, 0x58,
0x5a, 0x2f, 0xa4, 0xa3, 0xf6, 0x6d, 0xcd, 0xdc, 0xd6, 0x11, 0x47, 0x24, 0xd4, 0x73, 0xf7, 0x2a, 0x87, 0x8e, 0x63, 0xf7, 0xf8,
0xd7, 0x5f, 0x15, 0x7, 0xc0, 0x79, 0x19, 0xe4, 0x4f, 0x6a, 0x79, 0x55, 0xdb, 0xbb, 0x3f, 0x77, 0x77, 0x2e, 0x72, 0x2c, 0xf6,
0xf7, 0xb5, 0xd3, 0x8b, 0x86, 0xfa, 0x7b, 0x3b, 0x4a, 0x31, 0xb8, 0xab, 0x2b, 0x2c, 0x2e, 0x2e, 0xd3, 0xf, 0x8c, 0xc6, 0xe1,
0xb1, 0x30, 0xd5, 0x55, 0xb6, 0x2f, 0x1d, 0x6b, 0x67, 0x65, 0x4f, 0xb4, 0x51, 0xfb, 0x71, 0x54, 0xb5, 0x57, 0x2d, 0x5f, 0x29,
0x24, 0xae, 0xaa, 0x83, 0x7, 0x0, 0xe, 0x8f, 0xf5, 0x2f, 0xaa, 0xa7, 0x96, 0xfd, 0x3b, 0xe3, 0x8c, 0xfe, 0x29, 0x60, 0x73,
0x3a, 0x2e, 0xc5, 0xd2, 0x97, 0x18, 0x3d, 0xbb, 0x57, 0x93, 0x5a, 0xde, 0xf4, 0x7c, 0x76, 0xd7, 0x2f, 0xf4, 0x67, 0x78, 0xaf,
0x23, 0x26, 0xc1, 0xaf, 0x7e, 0x3a, 0xf6, 0x6a, 0x65, 0xab, 0x11, 0x34, 0x99, 0x7b, 0x9e, 0x62, 0x8a, 0xae, 0x2a, 0xe2, 0x1e,
0x26, 0xe6, 0x9a, 0x3d, 0xa9, 0xe2, 0x9a, 0x69, 0xf, 0x99, 0xf0, 0xfb, 0xd4, 0x9f, 0xc9, 0xef, 0x6, 0xb5, 0xbd, 0xc7, 0x54,
0xe8, 0x4c, 0xae, 0x9b, 0x8d, 0xc4, 0xef, 0x59, 0xcb, 0x1d, 0x87, 0x60, 0xa7, 0x66, 0xd4, 0x6c, 0xf6, 0x4b, 0x89, 0x72, 0x38,
0xeb, 0xe, 0x71, 0xb6, 0xbc, 0xdb, 0x4d, 0x75, 0x3c, 0x5f, 0x86, 0x86, 0x8b, 0x6a, 0xb9, 0xf7, 0xa6, 0x9e, 0x3f, 0x55, 0x5c,
0xfb, 0xf3, 0xcf, 0xf6, 0x7b, 0x6, 0xe1, 0x7f, 0xe6, 0xd, 0xf5, 0x1a, 0xff, 0x0, 0xf2, 0x8e, 0xa8, 0xff, 0x0, 0xf5, 0x66,
0x23, 0xff, 0x0, 0xeb, 0x7, 0x3e, 0x7c, 0xbe, 0xf3, 0x5f, 0xbd, 0x3c, 0xe1, 0xdd, 0x75, 0x8d, 0xfb, 0xbe, 0x72, 0x3a, 0xe6,
0x47, 0x61, 0xd4, 0x75, 0x7f, 0xe8, 0x7e, 0x1a, 0x4d, 0x6b, 0x5d, 0xb6, 0xd6, 0xed, 0x23, 0xc3, 0x7e, 0xd6, 0xc8, 0x66, 0xbe,
0x13, 0xda, 0xda, 0xcb, 0x2d, 0x33, 0xdc, 0x7e, 0x3b, 0x27, 0x2f, 0x3f, 0x73, 0x9e, 0x7d, 0xfe, 0x3e, 0xdc, 0x7f, 0x90, 0x35,
0x28, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd0,
0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff,
0xd1, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7,
0xff, 0xd2, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x7, 0xff, 0xd3, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x7, 0xff, 0xd4, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x7, 0xff, 0xd5, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x7, 0xff, 0xd6, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd7, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd0, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd1, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd2, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd3, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd4, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd5, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd6, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd7, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd0, 0xaf, 0xfc, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd1, 0xaf, 0xfc, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd2, 0xaf, 0xfc, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd3, 0xaf, 0xfc,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd4, 0xaf,
0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd5,
0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff,
0xd6, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7,
0xff, 0xd7, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x7, 0xff, 0xd0, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x7, 0xff, 0xd1, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x7, 0xff, 0xd2, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x7, 0xff, 0xd9
};

View File

@@ -1,452 +0,0 @@
/*****************************************************************************
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include <stdlib.h>
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <linux/videodev2.h>
#include "tools.h"
#include "logging.h"
#include "xioctl.h"
#include "device.h"
static const struct {
const char *name;
const v4l2_std_id standard;
} _STANDARDS[] = {
{"UNKNOWN", V4L2_STD_UNKNOWN},
{"PAL", V4L2_STD_PAL},
{"NTSC", V4L2_STD_NTSC},
{"SECAM", V4L2_STD_SECAM},
};
static const struct {
const char *name;
const unsigned format;
} _FORMATS[] = {
{"YUYV", V4L2_PIX_FMT_YUYV},
{"UYVY", V4L2_PIX_FMT_UYVY},
{"RGB565", V4L2_PIX_FMT_RGB565},
};
static int _device_open_check_cap(struct device_t *dev);
static int _device_open_dv_timings(struct device_t *dev);
static int _device_apply_dv_timings(struct device_t *dev);
static int _device_open_format(struct device_t *dev);
static void _device_open_alloc_picbufs(struct device_t *dev);
static int _device_open_mmap(struct device_t *dev);
static int _device_open_queue_buffers(struct device_t *dev);
static const char *_format_to_string_auto(char *buf, const size_t size, const unsigned format);
static const char *_format_to_string_null(const unsigned format);
static const char *_standard_to_string(const v4l2_std_id standard);
struct device_t *device_init() {
struct device_runtime_t *run;
struct device_t *dev;
A_CALLOC(run, 1);
run->fd = -1;
A_CALLOC(dev, 1);
dev->path = "/dev/video0";
dev->width = 640;
dev->height = 480;
dev->format = V4L2_PIX_FMT_YUYV;
dev->standard = V4L2_STD_UNKNOWN;
dev->n_buffers = max_u(sysconf(_SC_NPROCESSORS_ONLN), 1) + 1;
dev->n_workers = dev->n_buffers;
dev->timeout = 1;
dev->error_timeout = 1;
dev->run = run;
return dev;
}
void device_destroy(struct device_t *dev) {
free(dev->run);
free(dev);
}
int device_parse_format(const char *const str) {
for (unsigned index = 0; index < sizeof(_FORMATS) / sizeof(_FORMATS[0]); ++index) {
if (!strcasecmp(str, _FORMATS[index].name)) {
return _FORMATS[index].format;
}
}
return FORMAT_UNKNOWN;
}
v4l2_std_id device_parse_standard(const char *const str) {
for (unsigned index = 1; index < sizeof(_STANDARDS) / sizeof(_STANDARDS[0]); ++index) {
if (!strcasecmp(str, _STANDARDS[index].name)) {
return _STANDARDS[index].standard;
}
}
return STANDARD_UNKNOWN;
}
int device_open(struct device_t *dev) {
if ((dev->run->fd = open(dev->path, O_RDWR|O_NONBLOCK)) < 0) {
LOG_PERROR("Can't open device");
goto error;
}
LOG_INFO("Device fd=%d opened", dev->run->fd);
if (_device_open_check_cap(dev) < 0) {
goto error;
}
if (_device_open_dv_timings(dev) < 0) {
goto error;
}
if (_device_open_format(dev) < 0) {
goto error;
}
if (_device_open_mmap(dev) < 0) {
goto error;
}
if (_device_open_queue_buffers(dev) < 0) {
goto error;
}
_device_open_alloc_picbufs(dev);
dev->run->n_workers = dev->n_workers;
LOG_DEBUG("Device fd=%d initialized", dev->run->fd);
return 0;
error:
device_close(dev);
return -1;
}
void device_close(struct device_t *dev) {
if (dev->run->pictures) {
LOG_DEBUG("Releasing picture buffers ...");
for (unsigned index = 0; index < dev->run->n_buffers && dev->run->pictures[index].data; ++index) {
free(dev->run->pictures[index].data);
dev->run->pictures[index].data = NULL;
}
free(dev->run->pictures);
dev->run->pictures = NULL;
}
if (dev->run->hw_buffers) {
LOG_DEBUG("Unmapping HW buffers ...");
for (unsigned index = 0; index < dev->run->n_buffers; ++index) {
if (dev->run->hw_buffers[index].start != MAP_FAILED) {
if (munmap(dev->run->hw_buffers[index].start, dev->run->hw_buffers[index].length) < 0) {
LOG_PERROR("Can't unmap device buffer %d", index);
}
}
}
dev->run->n_buffers = 0;
free(dev->run->hw_buffers);
dev->run->hw_buffers = NULL;
}
if (dev->run->fd >= 0) {
LOG_DEBUG("Closing device ...");
if (close(dev->run->fd) < 0) {
LOG_PERROR("Can't close device fd=%d", dev->run->fd);
} else {
LOG_INFO("Device fd=%d closed", dev->run->fd);
}
dev->run->fd = -1;
}
}
static int _device_open_check_cap(struct device_t *dev) {
struct v4l2_capability cap;
MEMSET_ZERO(cap);
LOG_DEBUG("Calling ioctl(VIDIOC_QUERYCAP) ...");
if (xioctl(dev->run->fd, VIDIOC_QUERYCAP, &cap) < 0) {
LOG_PERROR("Can't query device (VIDIOC_QUERYCAP)");
return -1;
}
if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
LOG_ERROR("Video capture not supported by our device");
return -1;
}
if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
LOG_ERROR("Device does not support streaming IO");
return -1;
}
if (dev->standard != V4L2_STD_UNKNOWN) {
LOG_INFO("Using TV standard: %s", _standard_to_string(dev->standard));
if (xioctl(dev->run->fd, VIDIOC_S_STD, &dev->standard) < 0) {
LOG_PERROR("Can't set video standard");
return -1;
}
} else {
LOG_INFO("Using TV standard: DEFAULT");
}
return 0;
}
static int _device_open_dv_timings(struct device_t *dev) {
if (dev->dv_timings) {
LOG_DEBUG("Using DV-timings");
if (_device_apply_dv_timings(dev) < 0) {
return -1;
}
struct v4l2_event_subscription sub;
MEMSET_ZERO(sub);
sub.type = V4L2_EVENT_SOURCE_CHANGE;
LOG_DEBUG("Calling ioctl(VIDIOC_SUBSCRIBE_EVENT) ...");
if (xioctl(dev->run->fd, VIDIOC_SUBSCRIBE_EVENT, &sub) < 0) {
LOG_PERROR("Can't subscribe to V4L2_EVENT_SOURCE_CHANGE");
return -1;
}
} else {
dev->run->width = dev->width;
dev->run->height = dev->height;
}
return 0;
}
static int _device_apply_dv_timings(struct device_t *dev) {
struct v4l2_dv_timings dv_timings;
MEMSET_ZERO(dv_timings);
LOG_DEBUG("Calling ioctl(VIDIOC_QUERY_DV_TIMINGS) ...");
if (xioctl(dev->run->fd, VIDIOC_QUERY_DV_TIMINGS, &dv_timings) == 0) {
LOG_INFO(
"Got new DV timings: resolution=%dx%d; pixclk=%llu\n",
dv_timings.bt.width,
dv_timings.bt.height,
dv_timings.bt.pixelclock
);
LOG_DEBUG("Calling ioctl(VIDIOC_S_DV_TIMINGS) ...");
if (xioctl(dev->run->fd, VIDIOC_S_DV_TIMINGS, &dv_timings) < 0) {
LOG_PERROR("Failed to set DV timings");
return -1;
}
dev->run->width = dv_timings.bt.width;
dev->run->height = dv_timings.bt.height;
} else {
LOG_DEBUG("Calling ioctl(VIDIOC_QUERYSTD) ...");
if (xioctl(dev->run->fd, VIDIOC_QUERYSTD, &dev->standard) == 0) {
LOG_INFO("Applying the new VIDIOC_S_STD: %s ...", _standard_to_string(dev->standard));
if (xioctl(dev->run->fd, VIDIOC_S_STD, &dev->standard) < 0) {
LOG_PERROR("Can't set video standard");
return -1;
}
}
}
return 0;
}
static int _device_open_format(struct device_t *dev) {
struct v4l2_format fmt;
MEMSET_ZERO(fmt);
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = dev->run->width;
fmt.fmt.pix.height = dev->run->height;
fmt.fmt.pix.pixelformat = dev->format;
fmt.fmt.pix.field = V4L2_FIELD_ANY;
// Set format
LOG_DEBUG("Calling ioctl(VIDIOC_S_FMT) ...");
if (xioctl(dev->run->fd, VIDIOC_S_FMT, &fmt) < 0) {
char format_str[8];
LOG_PERROR(
"Unable to set format=%s; resolution=%dx%d",
_format_to_string_auto(format_str, 8, dev->format),
dev->run->width,
dev->run->height
);
return -1;
}
// Check resolution
if (fmt.fmt.pix.width != dev->run->width || fmt.fmt.pix.height != dev->run->height) {
LOG_ERROR("Requested resolution=%dx%d is unavailable", dev->run->width, dev->run->height);
}
dev->run->width = fmt.fmt.pix.width;
dev->run->height = fmt.fmt.pix.height;
LOG_INFO("Using resolution: %dx%d", dev->run->width, dev->run->height);
// Check format
if (fmt.fmt.pix.pixelformat != dev->format) {
char format_requested_str[8];
char format_obtained_str[8];
char *format_str_nullable;
LOG_ERROR(
"Could not obtain the requested pixelformat=%s; driver gave us %s",
_format_to_string_auto(format_requested_str, 8, dev->format),
_format_to_string_auto(format_obtained_str, 8, fmt.fmt.pix.pixelformat)
);
if ((format_str_nullable = (char *)_format_to_string_null(fmt.fmt.pix.pixelformat)) != NULL) {
LOG_INFO(
"Falling back to %s mode (consider using '--format=%s' option)",
format_str_nullable,
format_str_nullable
);
} else {
LOG_ERROR("Unsupported pixel format");
return -1;
}
}
dev->run->format = fmt.fmt.pix.pixelformat;
return 0;
}
static int _device_open_mmap(struct device_t *dev) {
struct v4l2_requestbuffers req;
MEMSET_ZERO(req);
req.count = dev->n_buffers;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
LOG_DEBUG("Calling ioctl(VIDIOC_REQBUFS) ...");
if (xioctl(dev->run->fd, VIDIOC_REQBUFS, &req)) {
LOG_PERROR("Device '%s' doesn't support memory mapping", dev->path);
return -1;
}
if (req.count < 1) {
LOG_ERROR("Insufficient buffer memory: %d", req.count);
return -1;
} else {
LOG_INFO("Requested %d HW buffers, got %d", dev->n_buffers, req.count);
}
LOG_DEBUG("Allocating HW buffers ...");
A_CALLOC(dev->run->hw_buffers, req.count);
for (dev->run->n_buffers = 0; dev->run->n_buffers < req.count; ++dev->run->n_buffers) {
struct v4l2_buffer buf_info;
MEMSET_ZERO(buf_info);
buf_info.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf_info.memory = V4L2_MEMORY_MMAP;
buf_info.index = dev->run->n_buffers;
LOG_DEBUG("Calling ioctl(VIDIOC_QUERYBUF) for device buffer %d ...", dev->run->n_buffers);
if (xioctl(dev->run->fd, VIDIOC_QUERYBUF, &buf_info) < 0) {
LOG_PERROR("Can't VIDIOC_QUERYBUF");
return -1;
}
LOG_DEBUG("Mapping device buffer %d ...", dev->run->n_buffers);
dev->run->hw_buffers[dev->run->n_buffers].length = buf_info.length;
dev->run->hw_buffers[dev->run->n_buffers].start = mmap(NULL, buf_info.length, PROT_READ|PROT_WRITE, MAP_SHARED, dev->run->fd, buf_info.m.offset);
if (dev->run->hw_buffers[dev->run->n_buffers].start == MAP_FAILED) {
LOG_PERROR("Can't map device buffer %d", dev->run->n_buffers);
return -1;
}
}
return 0;
}
static int _device_open_queue_buffers(struct device_t *dev) {
for (unsigned index = 0; index < dev->run->n_buffers; ++index) {
struct v4l2_buffer buf_info;
MEMSET_ZERO(buf_info);
buf_info.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf_info.memory = V4L2_MEMORY_MMAP;
buf_info.index = index;
LOG_DEBUG("Calling ioctl(VIDIOC_QBUF) for buffer %d ...", index);
if (xioctl(dev->run->fd, VIDIOC_QBUF, &buf_info) < 0) {
LOG_PERROR("Can't VIDIOC_QBUF");
return -1;
}
}
return 0;
}
static void _device_open_alloc_picbufs(struct device_t *dev) {
LOG_DEBUG("Allocating picture buffers ...");
A_CALLOC(dev->run->pictures, dev->run->n_buffers);
dev->run->max_picture_size = ((dev->run->width * dev->run->height) << 1) * 2;
for (unsigned index = 0; index < dev->run->n_buffers; ++index) {
LOG_DEBUG("Allocating picture buffer %d sized %lu bytes... ", index, dev->run->max_picture_size);
A_CALLOC(dev->run->pictures[index].data, dev->run->max_picture_size);
dev->run->pictures[index].allocated = dev->run->max_picture_size;
}
}
static const char *_format_to_string_auto(char *buf, const size_t size, const unsigned format) {
assert(size >= 8);
buf[0] = format & 0x7f;
buf[1] = (format >> 8) & 0x7f;
buf[2] = (format >> 16) & 0x7f;
buf[3] = (format >> 24) & 0x7f;
if (format & (1 << 31)) {
buf[4] = '-';
buf[5] = 'B';
buf[6] = 'E';
buf[7] = '\0';
} else {
buf[4] = '\0';
}
return buf;
}
static const char *_format_to_string_null(const unsigned format) {
for (unsigned index = 0; index < sizeof(_FORMATS) / sizeof(_FORMATS[0]); ++index) {
if (format == _FORMATS[index].format) {
return _FORMATS[index].name;
}
}
return NULL;
}
static const char *_standard_to_string(v4l2_std_id standard) {
for (unsigned index = 0; index < sizeof(_STANDARDS) / sizeof(_STANDARDS[0]); ++index) {
if (standard == _STANDARDS[index].standard) {
return _STANDARDS[index].name;
}
}
return _STANDARDS[0].name;
}

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

@@ -0,0 +1,79 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "file.h"
output_file_s *output_file_init(const char *path, bool json) {
output_file_s *output;
A_CALLOC(output, 1);
if (!strcmp(path, "-")) {
LOG_INFO("Using output: <stdout>");
output->fp = stdout;
} else {
LOG_INFO("Using output: %s", path);
if ((output->fp = fopen(path, "wb")) == NULL) {
LOG_PERROR("Can't open output file");
goto error;
}
}
output->json = json;
return output;
error:
output_file_destroy(output);
return NULL;
}
void output_file_write(void *v_output, const frame_s *frame) {
output_file_s *output = (output_file_s *)v_output;
if (output->json) {
base64_encode(frame->data, frame->used, &output->base64_data, &output->base64_allocated);
fprintf(output->fp,
"{\"size\": %zu, \"width\": %u, \"height\": %u,"
" \"format\": %u, \"stride\": %u, \"online\": %u,"
" \"grab_ts\": %.3Lf, \"encode_begin_ts\": %.3Lf, \"encode_end_ts\": %.3Lf,"
" \"data\": \"%s\"}\n",
frame->used, frame->width, frame->height,
frame->format, frame->stride, frame->online,
frame->grab_ts, frame->encode_begin_ts, frame->encode_end_ts,
output->base64_data);
} else {
fwrite(frame->data, 1, frame->used, output->fp);
}
fflush(output->fp);
}
void output_file_destroy(void *v_output) {
output_file_s *output = (output_file_s *)v_output;
if (output->base64_data) {
free(output->base64_data);
}
if (output->fp && output->fp != stdout) {
if (fclose(output->fp) < 0) {
LOG_PERROR("Can't close output file");
}
}
free(output);
}

View File

@@ -1,7 +1,8 @@
/*****************************************************************************
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -21,33 +22,28 @@
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <IL/OMX_Component.h>
#include <interface/vcos/vcos_semaphore.h>
#include <sys/types.h>
#include "../device.h"
#include "../libs/tools.h"
#include "../libs/logging.h"
#include "../libs/frame.h"
#include "../libs/base64.h"
struct omx_encoder_t {
OMX_HANDLETYPE encoder;
OMX_BUFFERHEADERTYPE *input_buffer;
OMX_BUFFERHEADERTYPE *output_buffer;
bool input_required;
bool output_available;
bool failed;
VCOS_SEMAPHORE_T handler_lock;
typedef struct {
const char *path;
bool json;
bool i_omx;
bool i_handler_lock;
bool i_encoder;
bool i_input_port_enabled;
bool i_output_port_enabled;
};
FILE *fp;
char *base64_data;
size_t base64_allocated;
} output_file_s;
struct omx_encoder_t *omx_encoder_init();
void omx_encoder_destroy(struct omx_encoder_t *omx);
int omx_encoder_prepare_for_device(struct omx_encoder_t *omx, struct device_t *dev, const unsigned quality, const bool use_ijg);
int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev, const unsigned index);
output_file_s *output_file_init(const char *path, bool json);
void output_file_write(void *v_output, const frame_s *frame);
void output_file_destroy(void *v_output);

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

@@ -0,0 +1,346 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <signal.h>
#include <limits.h>
#include <float.h>
#include <getopt.h>
#include <errno.h>
#include <assert.h>
#include "../libs/config.h"
#include "../libs/tools.h"
#include "../libs/logging.h"
#include "../libs/frame.h"
#include "../libs/memsink.h"
#include "../libs/options.h"
#include "file.h"
enum _OPT_VALUES {
_O_SINK = 's',
_O_SINK_TIMEOUT = 't',
_O_OUTPUT = 'o',
_O_OUTPUT_JSON = 'j',
_O_COUNT = 'c',
_O_INTERVAL = 'i',
_O_HELP = 'h',
_O_VERSION = 'v',
_O_LOG_LEVEL = 10000,
_O_PERF,
_O_VERBOSE,
_O_DEBUG,
_O_FORCE_LOG_COLORS,
_O_NO_LOG_COLORS,
};
static const struct option _LONG_OPTS[] = {
{"sink", required_argument, NULL, _O_SINK},
{"sink-timeout", required_argument, NULL, _O_SINK_TIMEOUT},
{"output", required_argument, NULL, _O_OUTPUT},
{"output-json", no_argument, NULL, _O_OUTPUT_JSON},
{"count", required_argument, NULL, _O_COUNT},
{"interval", required_argument, NULL, _O_INTERVAL},
{"log-level", required_argument, NULL, _O_LOG_LEVEL},
{"perf", no_argument, NULL, _O_PERF},
{"verbose", no_argument, NULL, _O_VERBOSE},
{"debug", no_argument, NULL, _O_DEBUG},
{"force-log-colors", no_argument, NULL, _O_FORCE_LOG_COLORS},
{"no-log-colors", no_argument, NULL, _O_NO_LOG_COLORS},
{"help", no_argument, NULL, _O_HELP},
{"version", no_argument, NULL, _O_VERSION},
{NULL, 0, NULL, 0},
};
volatile bool global_stop = false;
typedef struct {
void *v_output;
void (*write)(void *v_output, const frame_s *frame);
void (*destroy)(void *v_output);
} _output_context_s;
static void _signal_handler(int signum);
static void _install_signal_handlers(void);
static int _dump_sink(
const char *sink_name, unsigned sink_timeout,
long long count, long double interval,
_output_context_s *ctx);
static void _help(FILE *fp);
int main(int argc, char *argv[]) {
LOGGING_INIT;
A_THREAD_RENAME("main");
char *sink_name = NULL;
unsigned sink_timeout = 1;
char *output_path = NULL;
bool output_json = false;
long long count = 0;
long double interval = 0;
# define OPT_SET(_dest, _value) { \
_dest = _value; \
break; \
}
# define OPT_NUMBER(_name, _dest, _min, _max, _base) { \
errno = 0; char *_end = NULL; long long _tmp = strtoll(optarg, &_end, _base); \
if (errno || *_end || _tmp < _min || _tmp > _max) { \
printf("Invalid value for '%s=%s': min=%lld, max=%lld\n", _name, optarg, (long long)_min, (long long)_max); \
return 1; \
} \
_dest = _tmp; \
break; \
}
# define OPT_LDOUBLE(_name, _dest, _min, _max) { \
errno = 0; char *_end = NULL; long double _tmp = strtold(optarg, &_end); \
if (errno || *_end || _tmp < _min || _tmp > _max) { \
printf("Invalid value for '%s=%s': min=%Lf, max=%Lf\n", _name, optarg, (long double)_min, (long double)_max); \
return 1; \
} \
_dest = _tmp; \
break; \
}
char short_opts[128];
build_short_options(_LONG_OPTS, short_opts, 128);
for (int ch; (ch = getopt_long(argc, argv, short_opts, _LONG_OPTS, NULL)) >= 0;) {
switch (ch) {
case _O_SINK: OPT_SET(sink_name, optarg);
case _O_SINK_TIMEOUT: OPT_NUMBER("--sink-timeout", sink_timeout, 1, 60, 0);
case _O_OUTPUT: OPT_SET(output_path, optarg);
case _O_OUTPUT_JSON: OPT_SET(output_json, true);
case _O_COUNT: OPT_NUMBER("--count", count, 0, LLONG_MAX, 0);
case _O_INTERVAL: OPT_LDOUBLE("--interval", interval, 0, 60);
case _O_LOG_LEVEL: OPT_NUMBER("--log-level", us_log_level, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, 0);
case _O_PERF: OPT_SET(us_log_level, LOG_LEVEL_PERF);
case _O_VERBOSE: OPT_SET(us_log_level, LOG_LEVEL_VERBOSE);
case _O_DEBUG: OPT_SET(us_log_level, LOG_LEVEL_DEBUG);
case _O_FORCE_LOG_COLORS: OPT_SET(us_log_colored, true);
case _O_NO_LOG_COLORS: OPT_SET(us_log_colored, false);
case _O_HELP: _help(stdout); return 0;
case _O_VERSION: puts(VERSION); return 0;
case 0: break;
default: return 1;
}
}
# undef OPT_LDOUBLE
# undef OPT_NUMBER
# undef OPT_SET
if (sink_name == NULL || sink_name[0] == '\0') {
puts("Missing option --sink. See --help for details.");
return 1;
}
_output_context_s ctx = {0};
if (output_path && output_path[0] != '\0') {
if ((ctx.v_output = (void *)output_file_init(output_path, output_json)) == NULL) {
return 1;
}
ctx.write = output_file_write;
ctx.destroy = output_file_destroy;
}
_install_signal_handlers();
int retval = abs(_dump_sink(sink_name, sink_timeout, count, interval, &ctx));
if (ctx.v_output && ctx.destroy) {
ctx.destroy(ctx.v_output);
}
return retval;
}
static void _signal_handler(int signum) {
switch (signum) {
case SIGTERM: LOG_INFO_NOLOCK("===== Stopping by SIGTERM ====="); break;
case SIGINT: LOG_INFO_NOLOCK("===== Stopping by SIGINT ====="); break;
case SIGPIPE: LOG_INFO_NOLOCK("===== Stopping by SIGPIPE ====="); break;
default: LOG_INFO_NOLOCK("===== Stopping by %d =====", signum); break;
}
global_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));
LOG_DEBUG("Installing SIGINT handler ...");
assert(!sigaction(SIGINT, &sig_act, NULL));
LOG_DEBUG("Installing SIGTERM handler ...");
assert(!sigaction(SIGTERM, &sig_act, NULL));
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,
_output_context_s *ctx) {
if (count == 0) {
count = -1;
}
useconds_t interval_us = interval * 1000000;
frame_s *frame = frame_init();
memsink_s *sink = NULL;
if ((sink = memsink_init("input", sink_name, false, 0, false, 0, sink_timeout)) == NULL) {
goto error;
}
unsigned fps = 0;
unsigned fps_accum = 0;
long long fps_second = 0;
long double last_ts = 0;
while (!global_stop) {
int error = memsink_client_get(sink, frame);
if (error == 0) {
const long double now = get_now_monotonic();
const long long now_second = floor_ms(now);
char fourcc_str[8];
LOG_VERBOSE("Frame: size=%zu, res=%ux%u, fourcc=%s, stride=%u, online=%d, key=%d, latency=%.3Lf, diff=%.3Lf",
frame->used, frame->width, frame->height,
fourcc_to_string(frame->format, fourcc_str, 8),
frame->stride, frame->online, frame->key,
now - frame->grab_ts, (last_ts ? now - last_ts : 0));
last_ts = now;
LOG_DEBUG(" grab_ts=%.3Lf, encode_begin_ts=%.3Lf, encode_end_ts=%.3Lf",
frame->grab_ts, frame->encode_begin_ts, frame->encode_end_ts);
if (now_second != fps_second) {
fps = fps_accum;
fps_accum = 0;
fps_second = now_second;
LOG_PERF_FPS("A new second has come; captured_fps=%u", fps);
}
fps_accum += 1;
if (ctx->v_output) {
ctx->write(ctx->v_output, frame);
}
if (count >= 0) {
--count;
if (count <= 0) {
break;
}
}
if (interval_us > 0) {
usleep(interval_us);
}
} else if (error == -2) {
usleep(1000);
} else {
goto error;
}
}
int retval = 0;
goto ok;
error:
retval = -1;
ok:
if (sink) {
memsink_destroy(sink);
}
frame_destroy(frame);
LOG_INFO("Bye-bye");
return retval;
}
static void _help(FILE *fp) {
# define SAY(_msg, ...) fprintf(fp, _msg "\n", ##__VA_ARGS__)
SAY("\nuStreamer-dump - Dump uStreamer's memory sink to file");
SAY("═════════════════════════════════════════════════════");
SAY("Version: %s; license: GPLv3", VERSION);
SAY("Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com>\n");
SAY("Example:");
SAY("════════");
SAY(" ustreamer-dump --sink test --output - \\");
SAY(" | ffmpeg -use_wallclock_as_timestamps 1 -i pipe: -c:v libx264 test.mp4\n");
SAY("Sink options:");
SAY("═════════════");
SAY(" -s|--sink <name> ──────── Memory sink ID. No default.\n");
SAY(" -t|--sink-timeout <sec> ─ Timeout for the upcoming frame. Default: 1.\n");
SAY(" -o|--output <filename> ─── Filename to dump output to. Use '-' for stdout. Default: just consume the sink.\n");
SAY(" -j|--output-json ──────── Format output as JSON. Required option --output. Default: disabled.\n");
SAY(" -c|--count <N> ───────── Limit the number of frames. Default: 0 (infinite).\n");
SAY(" -i|--interval <sec> ───── Delay between reading frames (float). Default: 0.\n");
SAY("Logging options:");
SAY("════════════════");
SAY(" --log-level <N> ──── Verbosity level of messages from 0 (info) to 3 (debug).");
SAY(" Enabling debugging messages can slow down the program.");
SAY(" Available levels: 0 (info), 1 (performance), 2 (verbose), 3 (debug).");
SAY(" Default: %d.\n", us_log_level);
SAY(" --perf ───────────── Enable performance messages (same as --log-level=1). Default: disabled.\n");
SAY(" --verbose ────────── Enable verbose messages and lower (same as --log-level=2). Default: disabled.\n");
SAY(" --debug ──────────── Enable debug messages and lower (same as --log-level=3). Default: disabled.\n");
SAY(" --force-log-colors ─ Force color logging. Default: colored if stderr is a TTY.\n");
SAY(" --no-log-colors ──── Disable color logging. Default: ditto.\n");
SAY("Help options:");
SAY("═════════════");
SAY(" -h|--help ─────── Print this text and exit.\n");
SAY(" -v|--version ──── Print version and exit.\n");
# undef SAY
}

View File

@@ -1,155 +0,0 @@
/*****************************************************************************
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include <strings.h>
#include <assert.h>
#include "tools.h"
#include "logging.h"
#include "device.h"
#include "encoder.h"
#include "jpeg/encoder.h"
#ifdef OMX_ENCODER
# include "omx/encoder.h"
#endif
static const struct {
const char *name;
const enum encoder_type_t type;
} _ENCODER_TYPES[] = {
{"CPU", ENCODER_TYPE_CPU},
# ifdef OMX_ENCODER
{"OMX", ENCODER_TYPE_OMX},
# endif
};
struct encoder_t *encoder_init() {
struct encoder_t *encoder;
A_CALLOC(encoder, 1);
encoder->type = ENCODER_TYPE_CPU;
encoder->quality = 80;
return encoder;
}
void encoder_prepare(struct encoder_t *encoder) {
assert(encoder->type != ENCODER_TYPE_UNKNOWN);
if (encoder->type != ENCODER_TYPE_CPU) {
LOG_DEBUG("Initializing encoder ...");
}
LOG_INFO("Using JPEG quality: %d%%", encoder->quality);
# ifdef OMX_ENCODER
if (encoder->type == ENCODER_TYPE_OMX) {
if ((encoder->omx = omx_encoder_init()) == NULL) {
goto use_fallback;
}
}
# endif
return;
# pragma GCC diagnostic ignored "-Wunused-label"
# pragma GCC diagnostic push
use_fallback:
LOG_ERROR("Can't initialize selected encoder, using CPU instead it");
encoder->type = ENCODER_TYPE_CPU;
# pragma GCC diagnostic pop
}
void encoder_destroy(struct encoder_t *encoder) {
# ifdef OMX_ENCODER
if (encoder->omx) {
omx_encoder_destroy(encoder->omx);
}
# endif
free(encoder);
}
enum encoder_type_t encoder_parse_type(const char *const str) {
for (unsigned index = 0; index < sizeof(_ENCODER_TYPES) / sizeof(_ENCODER_TYPES[0]); ++index) {
if (!strcasecmp(str, _ENCODER_TYPES[index].name)) {
return _ENCODER_TYPES[index].type;
}
}
return ENCODER_TYPE_UNKNOWN;
}
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic push
void encoder_prepare_for_device(struct encoder_t *encoder, struct device_t *dev) {
assert(encoder->type != ENCODER_TYPE_UNKNOWN);
#pragma GCC diagnostic pop
# ifdef OMX_ENCODER
if (encoder->type == ENCODER_TYPE_OMX) {
if (omx_encoder_prepare_for_device(encoder->omx, dev, encoder->quality, encoder->omx_use_ijg) < 0) {
goto use_fallback;
}
if (dev->run->n_workers > 1) {
LOG_INFO("OMX encoder can only work with one worker thread; forcing n_workers to 1");
dev->run->n_workers = 1;
}
}
# endif
return;
# pragma GCC diagnostic ignored "-Wunused-label"
# pragma GCC diagnostic push
use_fallback:
LOG_ERROR("Can't prepare selected encoder, falling back to CPU");
encoder->type = ENCODER_TYPE_CPU;
dev->run->n_workers = dev->n_workers;
# pragma GCC diagnostic pop
}
int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, const unsigned index) {
assert(encoder->type != ENCODER_TYPE_UNKNOWN);
if (encoder->type == ENCODER_TYPE_CPU) {
jpeg_encoder_compress_buffer(dev, index, encoder->quality);
}
# ifdef OMX_ENCODER
else if (encoder->type == ENCODER_TYPE_OMX) {
if (omx_encoder_compress_buffer(encoder->omx, dev, index) < 0) {
goto error;
}
}
# endif
return 0;
# pragma GCC diagnostic ignored "-Wunused-label"
# pragma GCC diagnostic push
error:
LOG_INFO("HW compressing error, falling back to CPU");
encoder->type = ENCODER_TYPE_CPU;
dev->run->n_workers = dev->n_workers;
return -1;
# pragma GCC diagnostic pop
}

View File

@@ -1,460 +0,0 @@
/*****************************************************************************
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include <event2/event.h>
#include <event2/thread.h>
#include <event2/http.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>
#ifndef EVTHREAD_USE_PTHREADS_IMPLEMENTED
# error Required libevent-pthreads support
#endif
#include "tools.h"
#include "logging.h"
#include "stream.h"
#include "http.h"
#include "data/blank.h"
static void _http_callback_root(struct evhttp_request *request, void *arg);
static void _http_callback_ping(struct evhttp_request *request, void *v_server);
static void _http_callback_snapshot(struct evhttp_request *request, void *v_server);
static void _http_callback_stream(struct evhttp_request *request, void *v_server);
static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_ctx);
static void _http_callback_stream_error(struct bufferevent *buf_event, short what, void *v_ctx);
static void _http_exposed_refresh(int fd, short event, void *v_server);
static void _http_queue_send_stream(struct http_server_t *server, const bool updated);
static bool _expose_new_picture(struct http_server_t *server);
static bool _expose_blank_picture(struct http_server_t *server);
struct http_server_t *http_server_init(struct stream_t *stream) {
struct http_server_runtime_t *run;
struct http_server_t *server;
struct exposed_t *exposed;
struct timeval refresh_interval;
A_CALLOC(exposed, 1);
A_CALLOC(run, 1);
run->stream = stream;
run->exposed = exposed;
run->drop_same_frames_blank = 10;
A_CALLOC(server, 1);
server->host = "localhost";
server->port = 8080;
server->timeout = 10;
server->run = run;
_expose_blank_picture(server);
assert(!evthread_use_pthreads());
assert((run->base = event_base_new()));
assert((run->http = evhttp_new(run->base)));
evhttp_set_allowed_methods(run->http, EVHTTP_REQ_GET|EVHTTP_REQ_HEAD);
assert(!evhttp_set_cb(run->http, "/", _http_callback_root, NULL));
assert(!evhttp_set_cb(run->http, "/ping", _http_callback_ping, (void *)server));
assert(!evhttp_set_cb(run->http, "/snapshot", _http_callback_snapshot, (void *)exposed));
assert(!evhttp_set_cb(run->http, "/stream", _http_callback_stream, (void *)server));
refresh_interval.tv_sec = 0;
refresh_interval.tv_usec = 30000; // ~30 refreshes per second
assert((run->refresh = event_new(run->base, -1, EV_PERSIST, _http_exposed_refresh, server)));
assert(!event_add(run->refresh, &refresh_interval));
return server;
}
void http_server_destroy(struct http_server_t *server) {
event_del(server->run->refresh);
event_free(server->run->refresh);
evhttp_free(server->run->http);
event_base_free(server->run->base);
libevent_global_shutdown();
free(server->run->exposed->picture.data);
free(server->run->exposed);
free(server->run);
free(server);
}
int http_server_listen(struct http_server_t *server) {
server->run->drop_same_frames_blank = max_u(server->drop_same_frames, server->run->drop_same_frames_blank);
LOG_DEBUG("Binding HTTP to [%s]:%d ...", server->host, server->port);
evhttp_set_timeout(server->run->http, server->timeout);
if (evhttp_bind_socket(server->run->http, server->host, server->port) != 0) {
LOG_PERROR("Can't listen HTTP on [%s]:%d", server->host, server->port)
return -1;
}
LOG_INFO("Listening HTTP on [%s]:%d", server->host, server->port);
return 0;
}
void http_server_loop(struct http_server_t *server) {
LOG_INFO("Starting HTTP eventloop ...");
event_base_dispatch(server->run->base);
LOG_INFO("HTTP eventloop stopped");
}
void http_server_loop_break(struct http_server_t *server) {
event_base_loopbreak(server->run->base);
}
#define ADD_HEADER(_key, _value) \
assert(!evhttp_add_header(evhttp_request_get_output_headers(request), _key, _value))
#define PROCESS_HEAD_REQUEST { \
if (evhttp_request_get_command(request) == EVHTTP_REQ_HEAD) { \
evhttp_send_reply(request, HTTP_OK, "OK", NULL); \
return; \
} \
}
static void _http_callback_root(struct evhttp_request *request, UNUSED void *arg) {
struct evbuffer *buf;
PROCESS_HEAD_REQUEST;
assert((buf = evbuffer_new()));
assert(evbuffer_add_printf(buf,
"<!DOCTYPE html><html><head><meta charset=\"utf-8\">"
"<title>uStreamer</title></head><body><ul>"
"<li><a href=\"/ping\">/ping</a></li>"
"<li><a href=\"/snapshot\">/snapshot</a></li>"
"<li><a href=\"/stream\">/stream</a></li>"
"</body></html>"
));
ADD_HEADER("Content-Type", "text/html");
evhttp_send_reply(request, HTTP_OK, "OK", buf);
evbuffer_free(buf);
}
static void _http_callback_ping(struct evhttp_request *request, void *v_server) {
struct http_server_t *server = (struct http_server_t *)v_server;
struct evbuffer *buf;
PROCESS_HEAD_REQUEST;
assert((buf = evbuffer_new()));
assert(evbuffer_add_printf(buf,
"{\"stream\": {\"resolution\":"
" {\"width\": %u, \"height\": %u},"
" \"fps\": %u, \"online\": %s}}",
(server->fake_width ? server->fake_width : server->run->exposed->width),
(server->fake_height ? server->fake_height : server->run->exposed->height),
server->run->exposed->fps, (server->run->exposed->online ? "true" : "false")
));
ADD_HEADER("Content-Type", "application/json");
evhttp_send_reply(request, HTTP_OK, "OK", buf);
evbuffer_free(buf);
}
static void _http_callback_snapshot(struct evhttp_request *request, void *v_exposed) {
struct exposed_t *exposed = (struct exposed_t *)v_exposed;
struct evbuffer *buf;
char x_timestamp_buf[64];
PROCESS_HEAD_REQUEST;
assert((buf = evbuffer_new()));
assert(!evbuffer_add(buf, (const void *)exposed->picture.data, exposed->picture.size));
ADD_HEADER("Access-Control-Allow-Origin:", "*");
ADD_HEADER("Cache-Control", "no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0");
ADD_HEADER("Pragma", "no-cache");
ADD_HEADER("Expires", "Mon, 3 Jan 2000 12:34:56 GMT");
sprintf(x_timestamp_buf, "%.06Lf", now_real_ms());
ADD_HEADER("X-Timestamp", x_timestamp_buf);
ADD_HEADER("Content-Type", "image/jpeg");
evhttp_send_reply(request, HTTP_OK, "OK", buf);
evbuffer_free(buf);
}
#undef ADD_HEADER
static void _http_callback_stream(struct evhttp_request *request, void *v_server) {
// https://github.com/libevent/libevent/blob/29cc8386a2f7911eaa9336692a2c5544d8b4734f/http.c#L2814
// https://github.com/libevent/libevent/blob/29cc8386a2f7911eaa9336692a2c5544d8b4734f/http.c#L2789
// https://github.com/libevent/libevent/blob/29cc8386a2f7911eaa9336692a2c5544d8b4734f/http.c#L362
// https://github.com/libevent/libevent/blob/29cc8386a2f7911eaa9336692a2c5544d8b4734f/http.c#L791
// https://github.com/libevent/libevent/blob/29cc8386a2f7911eaa9336692a2c5544d8b4734f/http.c#L1458
struct http_server_t *server = (struct http_server_t *)v_server;
struct evhttp_connection *conn;
struct bufferevent *buf_event;
struct stream_client_t *client;
char *client_addr;
unsigned short client_port;
PROCESS_HEAD_REQUEST;
conn = evhttp_request_get_connection(request);
if (conn != NULL) {
A_CALLOC(client, 1);
client->server = server;
client->request = request;
client->need_initial = true;
client->need_first_frame = true;
if (server->run->stream_clients == NULL) {
server->run->stream_clients = client;
} else {
struct stream_client_t *last = server->run->stream_clients;
for (; last->next != NULL; last = last->next);
client->prev = last;
last->next = client;
}
server->run->stream_clients_count += 1;
evhttp_connection_get_peer(conn, &client_addr, &client_port);
LOG_INFO(
"HTTP: Registered the new stream client: [%s]:%u; clients now: %u",
client_addr, client_port, server->run->stream_clients_count
);
buf_event = evhttp_connection_get_bufferevent(conn);
bufferevent_setcb(buf_event, NULL, NULL, _http_callback_stream_error, (void *)client);
bufferevent_enable(buf_event, EV_READ);
} else {
evhttp_request_free(request);
}
}
#undef PROCESS_HEAD_REQUEST
#define BOUNDARY "boundarydonotcross"
#define RN "\r\n"
static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_client) {
struct stream_client_t *client = (struct stream_client_t *)v_client;
struct evbuffer *buf;
assert((buf = evbuffer_new()));
if (client->need_initial) {
assert(evbuffer_add_printf(buf,
"HTTP/1.0 200 OK" RN
"Access-Control-Allow-Origin: *" RN
"Cache-Control: no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0" RN
"Pragma: no-cache" RN
"Expires: Mon, 3 Jan 2000 12:34:56 GMT" RN
"Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY RN
RN
"--" BOUNDARY RN
));
assert(!bufferevent_write_buffer(buf_event, buf));
client->need_initial = false;
}
assert(evbuffer_add_printf(buf,
"Content-Type: image/jpeg" RN
"Content-Length: %lu" RN
"X-Timestamp: %.06Lf" RN
RN,
client->server->run->exposed->picture.size * sizeof(*client->server->run->exposed->picture.data),
now_real_ms()
));
assert(!evbuffer_add(buf,
(void *)client->server->run->exposed->picture.data,
client->server->run->exposed->picture.size * sizeof(*client->server->run->exposed->picture.data)
));
assert(evbuffer_add_printf(buf, RN "--" BOUNDARY RN));
assert(!bufferevent_write_buffer(buf_event, buf));
evbuffer_free(buf);
bufferevent_setcb(buf_event, NULL, NULL, _http_callback_stream_error, (void *)client);
bufferevent_enable(buf_event, EV_READ);
}
#undef BOUNDARY
#undef RN
static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UNUSED short what, void *v_client) {
struct stream_client_t *client = (struct stream_client_t *)v_client;
struct evhttp_connection *conn;
char *client_addr = "???";
unsigned short client_port = 0;
client->server->run->stream_clients_count -= 1;
conn = evhttp_request_get_connection(client->request);
if (conn != NULL) {
evhttp_connection_get_peer(conn, &client_addr, &client_port);
}
LOG_INFO(
"HTTP: Disconnected the stream client: [%s]:%u; clients now: %u",
client_addr, client_port, client->server->run->stream_clients_count
);
if (conn != NULL) {
evhttp_connection_free(conn);
}
if (client->prev == NULL) {
client->server->run->stream_clients = client->next;
} else {
client->prev->next = client->next;
}
if (client->next != NULL) {
client->next->prev = client->prev;
}
free(client);
}
static void _http_queue_send_stream(struct http_server_t *server, const bool updated) {
struct stream_client_t *client;
struct evhttp_connection *conn;
struct bufferevent *buf_event;
for (client = server->run->stream_clients; client != NULL; client = client->next) {
conn = evhttp_request_get_connection(client->request);
if (conn != NULL && (updated || client->need_first_frame)) {
buf_event = evhttp_connection_get_bufferevent(conn);
bufferevent_setcb(buf_event, NULL, _http_callback_stream_write, _http_callback_stream_error, (void *)client);
bufferevent_enable(buf_event, EV_READ|EV_WRITE);
client->need_first_frame = false;
}
}
}
static void _http_exposed_refresh(UNUSED int fd, UNUSED short what, void *v_server) {
struct http_server_t *server = (struct http_server_t *)v_server;
bool updated = false;
bool queue_send = false;
#define LOCK_STREAM \
A_PTHREAD_M_LOCK(&server->run->stream->mutex);
#define UNLOCK_STREAM \
{ server->run->stream->updated = false; A_PTHREAD_M_UNLOCK(&server->run->stream->mutex); }
if (server->run->stream->updated) {
LOG_DEBUG("Refreshing HTTP exposed ...");
LOCK_STREAM;
if (server->run->stream->picture.size > 0) { // If online
updated = _expose_new_picture(server);
UNLOCK_STREAM;
} else {
UNLOCK_STREAM;
updated = _expose_blank_picture(server);
}
queue_send = true;
} else if (!server->run->exposed->online) {
LOG_DEBUG("Refreshing HTTP exposed (BLANK) ...");
updated = _expose_blank_picture(server);
queue_send = true;
}
if (queue_send) {
_http_queue_send_stream(server, updated);
}
# undef LOCK_STREAM
# undef UNLOCK_STREAM
}
static bool _expose_new_picture(struct http_server_t *server) {
assert(server->run->stream->picture.size > 0);
server->run->exposed->fps = server->run->stream->fps;
# define MEM_STREAM_TO_EXPOSED \
server->run->exposed->picture.data, server->run->stream->picture.data, \
server->run->stream->picture.size * sizeof(*server->run->exposed->picture.data)
if (server->drop_same_frames) {
if (
server->run->exposed->online
&& server->run->exposed->dropped < server->drop_same_frames
&& server->run->exposed->picture.size == server->run->stream->picture.size
&& !memcmp(MEM_STREAM_TO_EXPOSED)
) {
LOG_PERF("HTTP: dropped same frame number %u", server->run->exposed->dropped);
server->run->exposed->dropped += 1;
return false; // Not updated
}
}
if (server->run->exposed->picture.allocated < server->run->stream->picture.allocated) {
A_REALLOC(server->run->exposed->picture.data, server->run->stream->picture.allocated);
server->run->exposed->picture.allocated = server->run->stream->picture.allocated;
}
memcpy(MEM_STREAM_TO_EXPOSED);
# undef MEM_STREAM_TO_EXPOSED
server->run->exposed->picture.size = server->run->stream->picture.size;
server->run->exposed->width = server->run->stream->width;
server->run->exposed->height = server->run->stream->height;
server->run->exposed->online = true;
server->run->exposed->dropped = 0;
return true; // Updated
}
static bool _expose_blank_picture(struct http_server_t *server) {
if (server->run->exposed->online || server->run->exposed->picture.size == 0) {
if (server->run->exposed->picture.allocated < BLANK_JPG_SIZE) {
A_REALLOC(server->run->exposed->picture.data, BLANK_JPG_SIZE);
server->run->exposed->picture.allocated = BLANK_JPG_SIZE;
}
memcpy(
server->run->exposed->picture.data, BLANK_JPG_DATA,
BLANK_JPG_SIZE * sizeof(*server->run->exposed->picture.data)
);
server->run->exposed->picture.size = BLANK_JPG_SIZE;
server->run->exposed->width = BLANK_JPG_WIDTH;
server->run->exposed->height = BLANK_JPG_HEIGHT;
server->run->exposed->fps = 0;
server->run->exposed->online = false;
goto updated;
}
if (server->run->exposed->dropped < server->run->drop_same_frames_blank) {
LOG_PERF("HTTP: dropped same frame (BLANK) number %u", server->run->exposed->dropped);
server->run->exposed->dropped += 1;
return false; // Not updated
}
updated:
server->run->exposed->dropped = 0;
return true; // Updated
}

View File

@@ -1,264 +0,0 @@
/*****************************************************************************
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# This source file based on code of MJPG-Streamer. #
# #
# Copyright (C) 2005-2006 Laurent Pinchart & Michel Xhaard #
# Copyright (C) 2006 Gabriel A. Devenyi #
# Copyright (C) 2007 Tom Stöveken #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <jpeglib.h>
#include <linux/videodev2.h>
#include "../tools.h"
#include "../device.h"
#include "encoder.h"
struct _mjpg_destination_mgr {
struct jpeg_destination_mgr mgr; // Default manager
JOCTET *buffer; // Start of buffer
unsigned char *outbuffer_cursor;
unsigned long *written;
};
static void _jpeg_set_dest_picture(j_compress_ptr jpeg, unsigned char *picture, unsigned long *written);
static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg,
unsigned char *line_buffer, const unsigned char *data,
const unsigned width, const unsigned height);
static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg,
unsigned char *line_buffer, const unsigned char *data,
const unsigned width, const unsigned height);
static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg,
unsigned char *line_buffer, const unsigned char *data,
const unsigned width, const unsigned height);
static void _jpeg_init_destination(j_compress_ptr jpeg);
static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg);
static void _jpeg_term_destination(j_compress_ptr jpeg);
void jpeg_encoder_compress_buffer(struct device_t *dev, const unsigned index, const unsigned quality) {
// This function based on compress_image_to_jpeg() from mjpg-streamer
struct jpeg_compress_struct jpeg;
struct jpeg_error_mgr jpeg_error;
unsigned char *line_buffer;
A_CALLOC(line_buffer, dev->run->width * 3);
jpeg.err = jpeg_std_error(&jpeg_error);
jpeg_create_compress(&jpeg);
dev->run->pictures[index].size = 0;
_jpeg_set_dest_picture(&jpeg, dev->run->pictures[index].data, &dev->run->pictures[index].size);
jpeg.image_width = dev->run->width;
jpeg.image_height = dev->run->height;
jpeg.input_components = 3;
jpeg.in_color_space = JCS_RGB;
jpeg_set_defaults(&jpeg);
jpeg_set_quality(&jpeg, quality, TRUE);
jpeg_start_compress(&jpeg, TRUE);
# define WRITE_SCANLINES(_func) \
_func(&jpeg, line_buffer, dev->run->hw_buffers[index].start, dev->run->width, dev->run->height)
switch (dev->run->format) {
// https://www.fourcc.org/yuv.php
case V4L2_PIX_FMT_YUYV: WRITE_SCANLINES(_jpeg_write_scanlines_yuyv); break;
case V4L2_PIX_FMT_UYVY: WRITE_SCANLINES(_jpeg_write_scanlines_uyvy); break;
case V4L2_PIX_FMT_RGB565: WRITE_SCANLINES(_jpeg_write_scanlines_rgb565); break;
default: assert(0 && "Unsupported input format for JPEG compressor");
}
# undef WRITE_SCANLINES
// TODO: process jpeg errors:
// https://stackoverflow.com/questions/19857766/error-handling-in-libjpeg
jpeg_finish_compress(&jpeg);
jpeg_destroy_compress(&jpeg);
free(line_buffer);
assert(dev->run->pictures[index].size > 0);
assert(dev->run->pictures[index].size <= dev->run->max_picture_size);
}
static void _jpeg_set_dest_picture(j_compress_ptr jpeg, unsigned char *picture, unsigned long *written) {
struct _mjpg_destination_mgr *dest;
if (jpeg->dest == NULL) {
assert((jpeg->dest = (struct jpeg_destination_mgr *)(*jpeg->mem->alloc_small)(
(j_common_ptr) jpeg, JPOOL_PERMANENT, sizeof(struct _mjpg_destination_mgr)
)));
}
dest = (struct _mjpg_destination_mgr *) jpeg->dest;
dest->mgr.init_destination = _jpeg_init_destination;
dest->mgr.empty_output_buffer = _jpeg_empty_output_buffer;
dest->mgr.term_destination = _jpeg_term_destination;
dest->outbuffer_cursor = picture;
dest->written = written;
}
static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg,
unsigned char *line_buffer, const unsigned char *data,
const unsigned width, const unsigned height) {
JSAMPROW scanlines[1];
unsigned z = 0;
while (jpeg->next_scanline < height) {
unsigned char *ptr = line_buffer;
for (unsigned x = 0; x < width; ++x) {
int y = (!z ? data[0] << 8 : data[2] << 8);
int u = data[1] - 128;
int v = data[3] - 128;
int r = (y + (359 * v)) >> 8;
int g = (y - (88 * u) - (183 * v)) >> 8;
int b = (y + (454 * u)) >> 8;
*(ptr++) = (r > 255) ? 255 : ((r < 0) ? 0 : r);
*(ptr++) = (g > 255) ? 255 : ((g < 0) ? 0 : g);
*(ptr++) = (b > 255) ? 255 : ((b < 0) ? 0 : b);
if (z++) {
z = 0;
data += 4;
}
}
scanlines[0] = line_buffer;
jpeg_write_scanlines(jpeg, scanlines, 1);
}
}
static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg,
unsigned char *line_buffer, const unsigned char *data,
const unsigned width, const unsigned height) {
JSAMPROW scanlines[1];
unsigned z = 0;
while(jpeg->next_scanline < height) {
unsigned char *ptr = line_buffer;
for(unsigned x = 0; x < width; ++x) {
int y = (!z ? data[1] << 8 : data[3] << 8);
int u = data[0] - 128;
int v = data[2] - 128;
int r = (y + (359 * v)) >> 8;
int g = (y - (88 * u) - (183 * v)) >> 8;
int b = (y + (454 * u)) >> 8;
*(ptr++) = (r > 255) ? 255 : ((r < 0) ? 0 : r);
*(ptr++) = (g > 255) ? 255 : ((g < 0) ? 0 : g);
*(ptr++) = (b > 255) ? 255 : ((b < 0) ? 0 : b);
if (z++) {
z = 0;
data += 4;
}
}
scanlines[0] = line_buffer;
jpeg_write_scanlines(jpeg, scanlines, 1);
}
}
static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg,
unsigned char *line_buffer, const unsigned char *data,
const unsigned width, const unsigned height) {
JSAMPROW scanlines[1];
while(jpeg->next_scanline < height) {
unsigned char *ptr = line_buffer;
for(unsigned x = 0; x < width; ++x) {
unsigned int two_byte = (data[1] << 8) + data[0];
*(ptr++) = data[1] & 248;
*(ptr++) = (unsigned char)((two_byte & 2016) >> 3);
*(ptr++) = (data[0] & 31) * 8;
data += 2;
}
scanlines[0] = line_buffer;
jpeg_write_scanlines(jpeg, scanlines, 1);
}
}
#define JPEG_OUTPUT_BUFFER_SIZE 4096
static void _jpeg_init_destination(j_compress_ptr jpeg) {
struct _mjpg_destination_mgr *dest = (struct _mjpg_destination_mgr *) jpeg->dest;
// Allocate the output buffer - it will be released when done with image
assert((dest->buffer = (JOCTET *)(*jpeg->mem->alloc_small)(
(j_common_ptr) jpeg, JPOOL_IMAGE, JPEG_OUTPUT_BUFFER_SIZE * sizeof(JOCTET)
)));
dest->mgr.next_output_byte = dest->buffer;
dest->mgr.free_in_buffer = JPEG_OUTPUT_BUFFER_SIZE;
}
static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg) {
// Called whenever local jpeg buffer fills up
struct _mjpg_destination_mgr *dest = (struct _mjpg_destination_mgr *) jpeg->dest;
memcpy(dest->outbuffer_cursor, dest->buffer, JPEG_OUTPUT_BUFFER_SIZE);
dest->outbuffer_cursor += JPEG_OUTPUT_BUFFER_SIZE;
*dest->written += JPEG_OUTPUT_BUFFER_SIZE;
dest->mgr.next_output_byte = dest->buffer;
dest->mgr.free_in_buffer = JPEG_OUTPUT_BUFFER_SIZE;
return TRUE;
}
static void _jpeg_term_destination(j_compress_ptr jpeg) {
// Called by jpeg_finish_compress after all data has been written.
// Usually needs to flush buffer
struct _mjpg_destination_mgr *dest = (struct _mjpg_destination_mgr *) jpeg->dest;
size_t data_count = JPEG_OUTPUT_BUFFER_SIZE - dest->mgr.free_in_buffer;
// Write any data remaining in the buffer
memcpy(dest->outbuffer_cursor, dest->buffer, data_count);
dest->outbuffer_cursor += data_count;
*dest->written += data_count;
}
#undef JPEG_OUTPUT_BUFFER_SIZE

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

@@ -0,0 +1,72 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "base64.h"
static const char _ENCODING_TABLE[] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '+', '/',
};
static const unsigned _MOD_TABLE[] = {0, 2, 1};
void base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *allocated) {
const size_t encoded_size = 4 * ((size + 2) / 3) + 1; // +1 for '\0'
if (*encoded == NULL || (allocated && *allocated < encoded_size)) {
A_REALLOC(*encoded, encoded_size);
if (allocated) {
*allocated = encoded_size;
}
}
for (unsigned data_index = 0, encoded_index = 0; data_index < size;) {
# define OCTET(_name) unsigned _name = (data_index < size ? (uint8_t)data[data_index++] : 0)
OCTET(octet_a);
OCTET(octet_b);
OCTET(octet_c);
# undef OCTET
unsigned triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
# define ENCODE(_offset) (*encoded)[encoded_index++] = _ENCODING_TABLE[(triple >> _offset * 6) & 0x3F]
ENCODE(3);
ENCODE(2);
ENCODE(1);
ENCODE(0);
# undef ENCODE
}
for (unsigned index = 0; index < _MOD_TABLE[size % 3]; index++) {
(*encoded)[encoded_size - 2 - index] = '=';
}
(*encoded)[encoded_size - 1] = '\0';
}

34
src/libs/base64.h Normal file
View File

@@ -0,0 +1,34 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
#include "tools.h"
void base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *allocated);

32
src/libs/config.h Normal file
View File

@@ -0,0 +1,32 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#define VERSION_MAJOR 5
#define VERSION_MINOR 1
#define MAKE_VERSION2(_major, _minor) #_major "." #_minor
#define MAKE_VERSION1(_major, _minor) MAKE_VERSION2(_major, _minor)
#define VERSION MAKE_VERSION1(VERSION_MAJOR, VERSION_MINOR)
#define VERSION_U ((unsigned)(VERSION_MAJOR * 1000 + VERSION_MINOR))

107
src/libs/frame.c Normal file
View File

@@ -0,0 +1,107 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "frame.h"
frame_s *frame_init(void) {
frame_s *frame;
A_CALLOC(frame, 1);
frame_realloc_data(frame, 512 * 1024);
frame->dma_fd = -1;
return frame;
}
void frame_destroy(frame_s *frame) {
if (frame->data) {
free(frame->data);
}
free(frame);
}
void frame_realloc_data(frame_s *frame, size_t size) {
if (frame->allocated < size) {
A_REALLOC(frame->data, size);
frame->allocated = size;
}
}
void frame_set_data(frame_s *frame, const uint8_t *data, size_t size) {
frame_realloc_data(frame, size);
memcpy(frame->data, data, size);
frame->used = size;
}
void frame_append_data(frame_s *frame, const uint8_t *data, size_t size) {
size_t new_used = frame->used + size;
frame_realloc_data(frame, new_used);
memcpy(frame->data + frame->used, data, size);
frame->used = new_used;
}
void frame_copy(const frame_s *src, frame_s *dest) {
frame_set_data(dest, src->data, src->used);
FRAME_COPY_META(src, dest);
}
bool frame_compare(const frame_s *a, const frame_s *b) {
return (
a->allocated && b->allocated
&& FRAME_COMPARE_META_USED_NOTS(a, b)
&& !memcmp(a->data, b->data, b->used)
);
}
unsigned frame_get_padding(const frame_s *frame) {
unsigned bytes_per_pixel = 0;
switch (frame->format) {
case V4L2_PIX_FMT_YUYV:
case V4L2_PIX_FMT_UYVY:
case V4L2_PIX_FMT_RGB565: bytes_per_pixel = 2; break;
case V4L2_PIX_FMT_RGB24: bytes_per_pixel = 3; break;
// case V4L2_PIX_FMT_H264:
case V4L2_PIX_FMT_MJPEG:
case V4L2_PIX_FMT_JPEG: bytes_per_pixel = 0; break;
default: assert(0 && "Unknown format");
}
if (bytes_per_pixel > 0 && frame->stride > frame->width) {
return (frame->stride - frame->width * bytes_per_pixel);
}
return 0;
}
const char *fourcc_to_string(unsigned format, char *buf, size_t size) {
assert(size >= 8);
buf[0] = format & 0x7F;
buf[1] = (format >> 8) & 0x7F;
buf[2] = (format >> 16) & 0x7F;
buf[3] = (format >> 24) & 0x7F;
if (format & ((unsigned)1 << 31)) {
buf[4] = '-';
buf[5] = 'B';
buf[6] = 'E';
buf[7] = '\0';
} else {
buf[4] = '\0';
}
return buf;
}

118
src/libs/frame.h Normal file
View File

@@ -0,0 +1,118 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <linux/videodev2.h>
#include "tools.h"
typedef struct {
uint8_t *data;
size_t used;
size_t allocated;
int dma_fd;
unsigned width;
unsigned height;
unsigned format;
unsigned stride;
// Stride is a bytesperline in V4L2
// https://www.kernel.org/doc/html/v4.14/media/uapi/v4l/pixfmt-v4l2.html
// https://medium.com/@oleg.shipitko/what-does-stride-mean-in-image-processing-bba158a72bcd
bool online;
bool key;
long double grab_ts;
long double encode_begin_ts;
long double encode_end_ts;
} frame_s;
#define FRAME_COPY_META(_src, _dest) { \
_dest->width = _src->width; \
_dest->height = _src->height; \
_dest->format = _src->format; \
_dest->stride = _src->stride; \
_dest->online = _src->online; \
_dest->key = _src->key; \
_dest->grab_ts = _src->grab_ts; \
_dest->encode_begin_ts = _src->encode_begin_ts; \
_dest->encode_end_ts = _src->encode_end_ts; \
}
static inline void frame_copy_meta(const frame_s *src, frame_s *dest) {
FRAME_COPY_META(src, dest);
}
#define FRAME_COMPARE_META_USED_NOTS(_a, _b) ( \
_a->used == _b->used \
&& _a->width == _b->width \
&& _a->height == _b->height \
&& _a->format == _b->format \
&& _a->stride == _b->stride \
&& _a->online == _b->online \
&& _a->key == _b->key \
)
static inline void frame_encoding_begin(const frame_s *src, frame_s *dest, unsigned format) {
assert(src->used > 0);
frame_copy_meta(src, dest);
dest->encode_begin_ts = get_now_monotonic();
dest->format = format;
dest->stride = 0;
dest->used = 0;
}
static inline void frame_encoding_end(frame_s *dest) {
assert(dest->used > 0);
dest->encode_end_ts = get_now_monotonic();
}
frame_s *frame_init(void);
void frame_destroy(frame_s *frame);
void frame_realloc_data(frame_s *frame, size_t size);
void frame_set_data(frame_s *frame, const uint8_t *data, size_t size);
void frame_append_data(frame_s *frame, const uint8_t *data, size_t size);
void frame_copy(const frame_s *src, frame_s *dest);
bool frame_compare(const frame_s *a, const frame_s *b);
unsigned frame_get_padding(const frame_s *frame);
const char *fourcc_to_string(unsigned format, char *buf, size_t size);
static inline bool is_jpeg(unsigned format) {
return (format == V4L2_PIX_FMT_JPEG || format == V4L2_PIX_FMT_MJPEG);
}

71
src/libs/list.h Normal file
View File

@@ -0,0 +1,71 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <assert.h>
#define LIST_STRUCT(...) \
__VA_ARGS__ *prev; \
__VA_ARGS__ *next;
#define LIST_ITERATE(_first, _item, ...) { \
for (__typeof__(_first) _item = _first; _item;) { \
__typeof__(_first) _next = _item->next; \
__VA_ARGS__ \
_item = _next; \
} \
}
#define LIST_APPEND(_first, _item) { \
if (_first == NULL) { \
_first = _item; \
} else { \
__typeof__(_first) _last = _first; \
for (; _last->next; _last = _last->next); \
_item->prev = _last; \
_last->next = _item; \
} \
}
#define LIST_APPEND_C(_first, _item, _count) { \
LIST_APPEND(_first, _item); \
++(_count); \
}
#define LIST_REMOVE(_first, _item) { \
if (_item->prev == NULL) { \
_first = _item->next; \
} else { \
_item->prev->next = _item->next; \
} \
if (_item->next != NULL) { \
_item->next->prev = _item->prev; \
} \
}
#define LIST_REMOVE_C(_first, _item, _count) { \
LIST_REMOVE(_first, _item); \
assert((_count) >= 1); \
--(_count); \
}

View File

@@ -1,7 +1,8 @@
/*****************************************************************************
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -19,6 +20,11 @@
*****************************************************************************/
#pragma once
#include "logging.h"
#define VERSION "0.13"
enum log_level_t us_log_level;
bool us_log_colored;
pthread_mutex_t us_log_mutex;

162
src/libs/logging.h Normal file
View File

@@ -0,0 +1,162 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <assert.h>
#include <pthread.h>
#include "tools.h"
#include "threading.h"
enum log_level_t {
LOG_LEVEL_INFO,
LOG_LEVEL_PERF,
LOG_LEVEL_VERBOSE,
LOG_LEVEL_DEBUG,
};
extern enum log_level_t us_log_level;
extern bool us_log_colored;
extern pthread_mutex_t us_log_mutex;
#define LOGGING_INIT { \
us_log_level = LOG_LEVEL_INFO; \
us_log_colored = isatty(2); \
A_MUTEX_INIT(&us_log_mutex); \
}
#define LOGGING_DESTROY A_MUTEX_DESTROY(&us_log_mutex)
#define LOGGING_LOCK A_MUTEX_LOCK(&us_log_mutex)
#define LOGGING_UNLOCK A_MUTEX_UNLOCK(&us_log_mutex)
#define COLOR_GRAY "\x1b[30;1m"
#define COLOR_RED "\x1b[31;1m"
#define COLOR_GREEN "\x1b[32;1m"
#define COLOR_YELLOW "\x1b[33;1m"
#define COLOR_BLUE "\x1b[34;1m"
#define COLOR_CYAN "\x1b[36;1m"
#define COLOR_RESET "\x1b[0m"
#define SEP_INFO(_ch) { \
LOGGING_LOCK; \
for (int _i = 0; _i < 80; ++_i) { \
fputc(_ch, stderr); \
} \
fputc('\n', stderr); \
fflush(stderr); \
LOGGING_UNLOCK; \
}
#define SEP_DEBUG(_ch) { \
if (us_log_level >= LOG_LEVEL_DEBUG) { \
SEP_INFO(_ch); \
} \
}
#define LOG_PRINTF_NOLOCK(_label_color, _label, _msg_color, _msg, ...) { \
char _tname_buf[MAX_THREAD_NAME] = {0}; \
thread_get_name(_tname_buf); \
if (us_log_colored) { \
fprintf(stderr, COLOR_GRAY "-- " _label_color _label COLOR_GRAY \
" [%.03Lf %9s]" " -- " COLOR_RESET _msg_color _msg COLOR_RESET, \
get_now_monotonic(), _tname_buf, ##__VA_ARGS__); \
} else { \
fprintf(stderr, "-- " _label " [%.03Lf %9s] -- " _msg, \
get_now_monotonic(), _tname_buf, ##__VA_ARGS__); \
} \
fputc('\n', stderr); \
fflush(stderr); \
}
#define LOG_PRINTF(_label_color, _label, _msg_color, _msg, ...) { \
LOGGING_LOCK; \
LOG_PRINTF_NOLOCK(_label_color, _label, _msg_color, _msg, ##__VA_ARGS__); \
LOGGING_UNLOCK; \
}
#define LOG_ERROR(_msg, ...) { \
LOG_PRINTF(COLOR_RED, "ERROR", COLOR_RED, _msg, ##__VA_ARGS__); \
}
#define LOG_PERROR(_msg, ...) { \
char _perror_buf[1024] = {0}; \
char *_perror_ptr = errno_to_string(errno, _perror_buf, 1024); \
LOG_ERROR(_msg ": %s", ##__VA_ARGS__, _perror_ptr); \
}
#define LOG_INFO(_msg, ...) { \
LOG_PRINTF(COLOR_GREEN, "INFO ", "", _msg, ##__VA_ARGS__); \
}
#define LOG_INFO_NOLOCK(_msg, ...) { \
LOG_PRINTF_NOLOCK(COLOR_GREEN, "INFO ", "", _msg, ##__VA_ARGS__); \
}
#define LOG_PERF(_msg, ...) { \
if (us_log_level >= LOG_LEVEL_PERF) { \
LOG_PRINTF(COLOR_CYAN, "PERF ", COLOR_CYAN, _msg, ##__VA_ARGS__); \
} \
}
#define LOG_PERF_FPS(_msg, ...) { \
if (us_log_level >= LOG_LEVEL_PERF) { \
LOG_PRINTF(COLOR_YELLOW, "PERF ", COLOR_YELLOW, _msg, ##__VA_ARGS__); \
} \
}
#define LOG_VERBOSE(_msg, ...) { \
if (us_log_level >= LOG_LEVEL_VERBOSE) { \
LOG_PRINTF(COLOR_BLUE, "VERB ", COLOR_BLUE, _msg, ##__VA_ARGS__); \
} \
}
#define LOG_VERBOSE_PERROR(_msg, ...) { \
if (us_log_level >= LOG_LEVEL_VERBOSE) { \
char _perror_buf[1024] = {0}; \
char *_perror_ptr = errno_to_string(errno, _perror_buf, 1023); \
LOG_PRINTF(COLOR_BLUE, "VERB ", COLOR_BLUE, _msg ": %s", ##__VA_ARGS__, _perror_ptr); \
} \
}
#define LOG_DEBUG(_msg, ...) { \
if (us_log_level >= LOG_LEVEL_DEBUG) { \
LOG_PRINTF(COLOR_GRAY, "DEBUG", COLOR_GRAY, _msg, ##__VA_ARGS__); \
} \
}

197
src/libs/memsink.c Normal file
View File

@@ -0,0 +1,197 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "memsink.h"
memsink_s *memsink_init(
const char *name, const char *obj, bool server,
mode_t mode, bool rm, unsigned client_ttl, unsigned timeout) {
memsink_s *sink;
A_CALLOC(sink, 1);
sink->name = name;
sink->obj = obj;
sink->server = server;
sink->rm = rm;
sink->client_ttl = client_ttl;
sink->timeout = timeout;
sink->fd = -1;
sink->mem = MAP_FAILED;
atomic_init(&sink->has_clients, false);
LOG_INFO("Using %s-sink: %s", name, obj);
mode_t mask = umask(0);
sink->fd = shm_open(sink->obj, (server ? O_RDWR | O_CREAT : O_RDWR), mode);
umask(mask);
if (sink->fd == -1) {
umask(mask);
LOG_PERROR("%s-sink: Can't open shared memory", name);
goto error;
}
if (sink->server && ftruncate(sink->fd, sizeof(memsink_shared_s)) < 0) {
LOG_PERROR("%s-sink: Can't truncate shared memory", name);
goto error;
}
if ((sink->mem = memsink_shared_map(sink->fd)) == NULL) {
LOG_PERROR("%s-sink: Can't mmap shared memory", name);
goto error;
}
return sink;
error:
memsink_destroy(sink);
return NULL;
}
void memsink_destroy(memsink_s *sink) {
if (sink->mem != MAP_FAILED) {
if (memsink_shared_unmap(sink->mem) < 0) {
LOG_PERROR("%s-sink: Can't unmap shared memory", sink->name);
}
}
if (sink->fd >= 0) {
if (close(sink->fd) < 0) {
LOG_PERROR("%s-sink: Can't close shared memory fd", sink->name);
}
if (sink->rm && shm_unlink(sink->obj) < 0) {
if (errno != ENOENT) {
LOG_PERROR("%s-sink: Can't remove shared memory", sink->name);
}
}
}
free(sink);
}
bool memsink_server_check(memsink_s *sink, const frame_s *frame) {
// Return true (the need to write to memsink) on any of these conditions:
// - EWOULDBLOCK - we have an active client;
// - Incorrect magic or version - need to first write;
// - We have some active clients by last_client_ts;
// - Frame meta differs (like size, format, but not timestamp).
assert(sink->server);
if (flock(sink->fd, LOCK_EX | LOCK_NB) < 0) {
if (errno == EWOULDBLOCK) {
atomic_store(&sink->has_clients, true);
return true;
}
LOG_PERROR("%s-sink: Can't lock memory", sink->name);
return false;
}
if (sink->mem->magic != MEMSINK_MAGIC || sink->mem->version != MEMSINK_VERSION) {
return true;
}
bool has_clients = (sink->mem->last_client_ts + sink->client_ttl > get_now_monotonic());
atomic_store(&sink->has_clients, has_clients);
if (flock(sink->fd, LOCK_UN) < 0) {
LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
return false;
}
return (has_clients || !FRAME_COMPARE_META_USED_NOTS(sink->mem, frame));;
}
int memsink_server_put(memsink_s *sink, const frame_s *frame) {
assert(sink->server);
const long double now = get_now_monotonic();
if (frame->used > MEMSINK_MAX_DATA) {
LOG_ERROR("%s-sink: Can't put frame: is too big (%zu > %zu)",
sink->name, frame->used, MEMSINK_MAX_DATA);
return 0; // -2
}
if (flock_timedwait_monotonic(sink->fd, 1) == 0) {
LOG_VERBOSE("%s-sink: >>>>> Exposing new frame ...", sink->name);
sink->last_id = get_now_id();
sink->mem->id = sink->last_id;
memcpy(sink->mem->data, frame->data, frame->used);
sink->mem->used = frame->used;
FRAME_COPY_META(frame, sink->mem);
sink->mem->magic = MEMSINK_MAGIC;
sink->mem->version = MEMSINK_VERSION;
atomic_store(&sink->has_clients, (sink->mem->last_client_ts + sink->client_ttl > get_now_monotonic()));
if (flock(sink->fd, LOCK_UN) < 0) {
LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
return -1;
}
LOG_VERBOSE("%s-sink: Exposed new frame; full exposition time = %.3Lf",
sink->name, get_now_monotonic() - now);
} else if (errno == EWOULDBLOCK) {
LOG_VERBOSE("%s-sink: ===== Shared memory is busy now; frame skipped", sink->name);
} else {
LOG_PERROR("%s-sink: Can't lock memory", sink->name);
return -1;
}
return 0;
}
int memsink_client_get(memsink_s *sink, frame_s *frame) { // cppcheck-suppress unusedFunction
assert(!sink->server); // Client only
if (flock_timedwait_monotonic(sink->fd, sink->timeout) < 0) {
if (errno == EWOULDBLOCK) {
return -2;
}
LOG_PERROR("%s-sink: Can't lock memory", sink->name);
return -1;
}
int retval = -2; // Not updated
if (sink->mem->magic == MEMSINK_MAGIC) {
if (sink->mem->version != MEMSINK_VERSION) {
LOG_ERROR("%s-sink: Protocol version mismatch: sink=%u, required=%u",
sink->name, sink->mem->version, MEMSINK_VERSION);
retval = -1;
goto done;
}
if (sink->mem->id != sink->last_id) { // When updated
sink->last_id = sink->mem->id;
frame_set_data(frame, sink->mem->data, sink->mem->used);
FRAME_COPY_META(sink->mem, frame);
retval = 0;
}
sink->mem->last_client_ts = get_now_monotonic();
}
done:
if (flock(sink->fd, LOCK_UN) < 0) {
LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
return -1;
}
return retval;
}

68
src/libs/memsink.h Normal file
View File

@@ -0,0 +1,68 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <stdatomic.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include "tools.h"
#include "logging.h"
#include "frame.h"
#include "memsinksh.h"
typedef struct {
const char *name;
const char *obj;
bool server;
bool rm;
unsigned client_ttl; // Only for server
unsigned timeout;
int fd;
memsink_shared_s *mem;
uint64_t last_id;
atomic_bool has_clients; // Only for server
} memsink_s;
memsink_s *memsink_init(
const char *name, const char *obj, bool server,
mode_t mode, bool rm, unsigned client_ttl, unsigned timeout);
void memsink_destroy(memsink_s *sink);
bool memsink_server_check(memsink_s *sink, const frame_s *frame);
int memsink_server_put(memsink_s *sink, const frame_s *frame);
int memsink_client_get(memsink_s *sink, frame_s *frame);

84
src/libs/memsinksh.h Normal file
View File

@@ -0,0 +1,84 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/mman.h>
#define MEMSINK_MAGIC ((uint64_t)0xCAFEBABECAFEBABE)
#define MEMSINK_VERSION ((uint32_t)2)
#ifndef CFG_MEMSINK_MAX_DATA
# define CFG_MEMSINK_MAX_DATA 33554432
#endif
#define MEMSINK_MAX_DATA ((size_t)(CFG_MEMSINK_MAX_DATA))
typedef struct {
uint64_t magic;
uint32_t version;
uint64_t id;
size_t used;
unsigned width;
unsigned height;
unsigned format;
unsigned stride;
bool online;
bool key;
long double grab_ts;
long double encode_begin_ts;
long double encode_end_ts;
long double last_client_ts;
uint8_t data[MEMSINK_MAX_DATA];
} memsink_shared_s;
INLINE memsink_shared_s *memsink_shared_map(int fd) {
memsink_shared_s *mem = mmap(
NULL,
sizeof(memsink_shared_s),
PROT_READ | PROT_WRITE,
MAP_SHARED,
fd,
0
);
if (mem == MAP_FAILED) {
return NULL;
}
assert(mem != NULL);
return mem;
}
INLINE int memsink_shared_unmap(memsink_shared_s *mem) {
assert(mem != NULL);
return munmap(mem, sizeof(memsink_shared_s));
}

39
src/libs/options.c Normal file
View File

@@ -0,0 +1,39 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "options.h"
void build_short_options(const struct option opts[], char *short_opts, size_t size) {
memset(short_opts, 0, size);
for (unsigned short_index = 0, opt_index = 0; opts[opt_index].name != NULL; ++opt_index) {
assert(short_index < size - 3);
if (isalpha(opts[opt_index].val)) {
short_opts[short_index] = opts[opt_index].val;
++short_index;
if (opts[opt_index].has_arg == required_argument) {
short_opts[short_index] = ':';
++short_index;
}
}
}
}

33
src/libs/options.h Normal file
View File

@@ -0,0 +1,33 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <string.h>
#include <ctype.h>
#include <getopt.h>
#include <assert.h>
#include <sys/types.h>
void build_short_options(const struct option opts[], char *short_opts, size_t size);

147
src/libs/process.h Normal file
View File

@@ -0,0 +1,147 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#if defined(__linux__)
# define HAS_PDEATHSIG
#elif defined(__FreeBSD__)
# include <sys/param.h>
# if __FreeBSD_version >= 1102000
# define HAS_PDEATHSIG
# endif
#endif
#ifdef WITH_SETPROCTITLE
# include <stdlib.h>
# include <string.h>
# if defined(__linux__)
# include <bsd/unistd.h>
# elif (defined(__FreeBSD__) || defined(__DragonFly__))
//# include <unistd.h>
//# include <sys/types.h>
# elif (defined(__NetBSD__) || defined(__OpenBSD__)) // setproctitle() placed in stdlib.h
# else
# error setproctitle() not implemented, you can disable it using WITH_SETPROCTITLE=0
# endif
#endif
#ifdef HAS_PDEATHSIG
# if defined(__linux__)
# include <sys/prctl.h>
# elif defined(__FreeBSD__)
# include <sys/procctl.h>
# endif
#endif
#ifdef WITH_SETPROCTITLE
# include "tools.h"
#endif
#ifdef HAS_PDEATHSIG
# include "logging.h"
#endif
#ifdef WITH_SETPROCTITLE
extern char **environ;
#endif
#ifdef HAS_PDEATHSIG
INLINE int process_track_parent_death(void) {
pid_t parent = getppid();
int signum = SIGTERM;
# if defined(__linux__)
int retval = prctl(PR_SET_PDEATHSIG, signum);
# elif defined(__FreeBSD__)
int retval = procctl(P_PID, 0, PROC_PDEATHSIG_CTL, &signum);
# else
# error WTF?
# endif
if (retval < 0) {
LOG_PERROR("Can't set to receive SIGTERM on parent process death");
return -1;
}
if (kill(parent, 0) < 0) {
LOG_PERROR("The parent process %d is already dead", parent);
return -1;
}
return 0;
}
#endif
#ifdef WITH_SETPROCTITLE
# pragma GCC diagnostic ignored "-Wunused-parameter"
# pragma GCC diagnostic push
INLINE void process_set_name_prefix(int argc, char *argv[], const char *prefix) {
# pragma GCC diagnostic pop
char *cmdline = NULL;
size_t allocated = 2048;
size_t used = 0;
A_REALLOC(cmdline, allocated);
cmdline[0] = '\0';
for (int index = 0; index < argc; ++index) {
size_t arg_len = strlen(argv[index]);
if (used + arg_len + 16 >= allocated) {
allocated += arg_len + 2048;
A_REALLOC(cmdline, allocated); // cppcheck-suppress memleakOnRealloc // False-positive (ok with assert)
}
strcat(cmdline, " ");
strcat(cmdline, argv[index]);
used = strlen(cmdline); // Не считаем вручную, так надежнее
}
# ifdef __linux__
setproctitle_init(argc, argv, environ);
# endif
setproctitle("-%s:%s", prefix, cmdline);
free(cmdline);
}
#endif
INLINE void process_notify_parent(void) {
pid_t parent = getppid();
if (kill(parent, SIGUSR2) < 0) {
LOG_PERROR("Can't send SIGUSR2 to the parent process %d", parent);
}
}
INLINE void process_suicide(void) {
pid_t pid = getpid();
if (kill(pid, SIGTERM) < 0) {
LOG_PERROR("Can't send SIGTERM to own pid %d", pid);
}
}

125
src/libs/threading.h Normal file
View File

@@ -0,0 +1,125 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <pthread.h>
#ifdef WITH_PTHREAD_NP
# if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
# include <pthread_np.h>
# include <sys/param.h>
# endif
#endif
#include "tools.h"
#ifdef PTHREAD_MAX_NAMELEN_NP
# define MAX_THREAD_NAME ((size_t)(PTHREAD_MAX_NAMELEN_NP))
#else
# define MAX_THREAD_NAME ((size_t)16)
#endif
#define A_THREAD_CREATE(_tid, _func, _arg) assert(!pthread_create(_tid, NULL, _func, _arg))
#define A_THREAD_JOIN(_tid) assert(!pthread_join(_tid, NULL))
#ifdef WITH_PTHREAD_NP
# define A_THREAD_RENAME(_fmt, ...) { \
char _new_tname_buf[MAX_THREAD_NAME] = {0}; \
assert(snprintf(_new_tname_buf, MAX_THREAD_NAME, _fmt, ##__VA_ARGS__) > 0); \
thread_set_name(_new_tname_buf); \
}
#else
# define A_THREAD_RENAME(_fmt, ...)
#endif
#define A_MUTEX_INIT(_mutex) assert(!pthread_mutex_init(_mutex, NULL))
#define A_MUTEX_DESTROY(_mutex) assert(!pthread_mutex_destroy(_mutex))
#define A_MUTEX_LOCK(_mutex) assert(!pthread_mutex_lock(_mutex))
#define A_MUTEX_UNLOCK(_mutex) assert(!pthread_mutex_unlock(_mutex))
#define A_COND_INIT(_cond) assert(!pthread_cond_init(_cond, NULL))
#define A_COND_DESTROY(_cond) assert(!pthread_cond_destroy(_cond))
#define A_COND_SIGNAL(...) assert(!pthread_cond_signal(__VA_ARGS__))
#define A_COND_WAIT_TRUE(_var, _cond, _mutex) { while(!_var) assert(!pthread_cond_wait(_cond, _mutex)); }
#ifdef WITH_PTHREAD_NP
INLINE void thread_set_name(const char *name) {
# if defined(__linux__)
pthread_setname_np(pthread_self(), name);
# elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
pthread_set_name_np(pthread_self(), name);
# elif defined(__NetBSD__)
pthread_setname_np(pthread_self(), "%s", (void *)name);
# else
# error thread_set_name() not implemented, you can disable it using WITH_PTHREAD_NP=0
# endif
}
#endif
INLINE void 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, MAX_THREAD_NAME);
# elif \
(defined(__FreeBSD__) && defined(__FreeBSD_version) && __FreeBSD_version >= 1103500) \
|| (defined(__OpenBSD__) && defined(OpenBSD) && OpenBSD >= 201905) \
|| defined(__DragonFly__)
pthread_get_name_np(pthread_self(), name, MAX_THREAD_NAME);
if (name[0] != '\0') {
retval = 0;
}
# else
# error thread_get_name() not implemented, you can disable it using WITH_PTHREAD_NP=0
# endif
if (retval < 0) {
#endif
#if defined(__linux__)
pid_t tid = syscall(SYS_gettid);
#elif defined(__FreeBSD__)
pid_t tid = syscall(SYS_thr_self);
#elif defined(__OpenBSD__)
pid_t tid = syscall(SYS_getthrid);
#elif defined(__NetBSD__)
pid_t tid = syscall(SYS__lwp_self);
#elif defined(__DragonFly__)
pid_t tid = syscall(SYS_lwp_gettid);
#else
pid_t tid = 0; // Makes cppcheck happy
# warning gettid() not implemented
#endif
assert(snprintf(name, MAX_THREAD_NAME, "tid=%d", tid) > 0);
#ifdef WITH_PTHREAD_NP
}
#endif
}

166
src/libs/tools.h Normal file
View File

@@ -0,0 +1,166 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <locale.h>
#include <errno.h>
#include <math.h>
#include <time.h>
#include <assert.h>
#include <sys/file.h>
#define RN "\r\n"
#define INLINE inline __attribute__((always_inline))
#define UNUSED __attribute__((unused))
#define A_CALLOC(_dest, _nmemb) assert((_dest = calloc(_nmemb, sizeof(*(_dest)))))
#define A_REALLOC(_dest, _nmemb) assert((_dest = realloc(_dest, _nmemb * sizeof(*(_dest)))))
#define MEMSET_ZERO(_obj) memset(&(_obj), 0, sizeof(_obj))
#define A_ASPRINTF(_dest, _fmt, ...) assert(asprintf(&(_dest), _fmt, ##__VA_ARGS__) >= 0)
#define ARRAY_LEN(_array) (sizeof(_array) / sizeof(_array[0]))
INLINE const char *bool_to_string(bool flag) {
return (flag ? "true" : "false");
}
INLINE size_t align_size(size_t size, size_t to) {
return ((size + (to - 1)) & ~(to - 1));
}
INLINE unsigned min_u(unsigned a, unsigned b) {
return (a < b ? a : b);
}
INLINE unsigned max_u(unsigned a, unsigned b) {
return (a > b ? a : b);
}
INLINE long long floor_ms(long double now) {
return (long long)now - (now < (long long)now); // floor()
}
INLINE uint32_t triple_u32(uint32_t x) {
// https://nullprogram.com/blog/2018/07/31/
x ^= x >> 17;
x *= UINT32_C(0xED5AD4BB);
x ^= x >> 11;
x *= UINT32_C(0xAC4C1B51);
x ^= x >> 15;
x *= UINT32_C(0x31848BAB);
x ^= x >> 14;
return x;
}
INLINE void get_now(clockid_t clk_id, time_t *sec, long *msec) {
struct timespec ts;
assert(!clock_gettime(clk_id, &ts));
*sec = ts.tv_sec;
*msec = round(ts.tv_nsec / 1.0e6);
if (*msec > 999) {
*sec += 1;
*msec = 0;
}
}
#if defined(CLOCK_MONOTONIC_RAW)
# define X_CLOCK_MONOTONIC CLOCK_MONOTONIC_RAW
#elif defined(CLOCK_MONOTONIC_FAST)
# define X_CLOCK_MONOTONIC CLOCK_MONOTONIC_FAST
#else
# define X_CLOCK_MONOTONIC CLOCK_MONOTONIC
#endif
INLINE long double get_now_monotonic(void) {
time_t sec;
long msec;
get_now(X_CLOCK_MONOTONIC, &sec, &msec);
return (long double)sec + ((long double)msec) / 1000;
}
INLINE uint64_t get_now_monotonic_u64(void) {
struct timespec ts;
assert(!clock_gettime(X_CLOCK_MONOTONIC, &ts));
return (uint64_t)(ts.tv_nsec / 1000) + (uint64_t)ts.tv_sec * 1000000;
}
INLINE uint64_t get_now_id(void) {
uint64_t now = get_now_monotonic_u64();
return (uint64_t)triple_u32(now) | ((uint64_t)triple_u32(now + 12345) << 32);
}
#undef X_CLOCK_MONOTONIC
INLINE long double get_now_real(void) {
time_t sec;
long msec;
get_now(CLOCK_REALTIME, &sec, &msec);
return (long double)sec + ((long double)msec) / 1000;
}
INLINE unsigned get_cores_available(void) {
long cores_sysconf = sysconf(_SC_NPROCESSORS_ONLN);
cores_sysconf = (cores_sysconf < 0 ? 0 : cores_sysconf);
return max_u(min_u(cores_sysconf, 4), 1);
}
INLINE int flock_timedwait_monotonic(int fd, long double timeout) {
long double deadline_ts = get_now_monotonic() + timeout;
int retval = -1;
while (true) {
retval = flock(fd, LOCK_EX | LOCK_NB);
if (retval == 0 || errno != EWOULDBLOCK || get_now_monotonic() > deadline_ts) {
break;
}
if (usleep(1000) < 0) {
break;
}
}
return retval;
}
INLINE char *errno_to_string(int error, char *buf, size_t size) {
assert(buf);
assert(size > 0);
locale_t locale = newlocale(LC_MESSAGES_MASK, "C", NULL);
char *str = "!!! newlocale() error !!!";
strncpy(buf, (locale ? strerror_l(error, locale) : str), size - 1);
buf[size - 1] = '\0';
if (locale) {
freelocale(locale);
}
return buf;
}

92
src/libs/unjpeg.c Normal file
View File

@@ -0,0 +1,92 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "unjpeg.h"
typedef struct {
struct jpeg_error_mgr mgr; // Default manager
jmp_buf jmp;
const frame_s *frame;
} _jpeg_error_manager_s;
static void _jpeg_error_handler(j_common_ptr jpeg);
int unjpeg(const frame_s *src, frame_s *dest, bool decode) {
assert(is_jpeg(src->format));
volatile int retval = 0;
struct jpeg_decompress_struct jpeg;
jpeg_create_decompress(&jpeg);
// https://stackoverflow.com/questions/19857766/error-handling-in-libjpeg
_jpeg_error_manager_s jpeg_error;
jpeg.err = jpeg_std_error((struct jpeg_error_mgr *)&jpeg_error);
jpeg_error.mgr.error_exit = _jpeg_error_handler;
jpeg_error.frame = src;
if (setjmp(jpeg_error.jmp) < 0) {
retval = -1;
goto done;
}
jpeg_mem_src(&jpeg, src->data, src->used);
jpeg_read_header(&jpeg, TRUE);
jpeg.out_color_space = JCS_RGB;
jpeg_start_decompress(&jpeg);
frame_copy_meta(src, dest);
dest->format = V4L2_PIX_FMT_RGB24;
dest->width = jpeg.output_width;
dest->height = jpeg.output_height;
dest->stride = jpeg.output_width * jpeg.output_components; // Row stride
dest->used = 0;
if (decode) {
JSAMPARRAY scanlines;
scanlines = (*jpeg.mem->alloc_sarray)((j_common_ptr) &jpeg, JPOOL_IMAGE, dest->stride, 1);
frame_realloc_data(dest, ((dest->width * dest->height) << 1) * 2);
while (jpeg.output_scanline < jpeg.output_height) {
jpeg_read_scanlines(&jpeg, scanlines, 1);
frame_append_data(dest, scanlines[0], dest->stride);
}
jpeg_finish_decompress(&jpeg);
}
done:
jpeg_destroy_decompress(&jpeg);
return retval;
}
static void _jpeg_error_handler(j_common_ptr jpeg) {
_jpeg_error_manager_s *jpeg_error = (_jpeg_error_manager_s *)jpeg->err;
char msg[JMSG_LENGTH_MAX];
(*jpeg_error->mgr.format_message)(jpeg, msg);
LOG_ERROR("Can't decompress JPEG: %s", msg);
longjmp(jpeg_error->jmp, -1);
}

View File

@@ -1,7 +1,8 @@
/*****************************************************************************
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -22,24 +23,18 @@
#pragma once
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <setjmp.h>
#include <assert.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <IL/OMX_IVCommon.h>
#include <IL/OMX_Core.h>
#include <IL/OMX_Image.h>
#include <jpeglib.h>
#include <linux/videodev2.h>
#include "../logging.h"
#include "../tools.h"
#include "logging.h"
#include "frame.h"
#define LOG_OMX_ERROR(_error, _msg, ...) { \
LOGGING_LOCK; \
printf("-- ERROR [%.03Lf tid=%ld] -- " _msg ": %s\n", now_monotonic_ms(), \
syscall(SYS_gettid), ##__VA_ARGS__, omx_error_to_string(_error)); \
LOGGING_UNLOCK; \
}
const char *omx_error_to_string(const OMX_ERRORTYPE error);
const char *omx_state_to_string(const OMX_STATETYPE state);
int unjpeg(const frame_s *src, frame_s *dest, bool decode);

View File

@@ -1,118 +0,0 @@
/*****************************************************************************
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <assert.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include "tools.h"
unsigned log_level;
pthread_mutex_t log_mutex;
#define LOG_LEVEL_INFO 0
#define LOG_LEVEL_PERF 1
#define LOG_LEVEL_VERBOSE 2
#define LOG_LEVEL_DEBUG 3
#define LOGGING_INIT assert(!pthread_mutex_init(&log_mutex, NULL))
#define LOGGING_DESTROY assert(!pthread_mutex_destroy(&log_mutex))
#define LOGGING_LOCK assert(!pthread_mutex_lock(&log_mutex))
#define LOGGING_UNLOCK assert(!pthread_mutex_unlock(&log_mutex))
#define SEP_INFO(_x_ch) { \
LOGGING_LOCK; \
for (int _i = 0; _i < 80; ++_i) { \
putchar(_x_ch); \
} \
putchar('\n'); \
LOGGING_UNLOCK; \
}
#define SEP_DEBUG(_x_ch) { \
if (log_level >= LOG_LEVEL_DEBUG) { \
SEP_INFO(_x_ch); \
} \
}
#define LOG_ERROR(_x_msg, ...) { \
LOGGING_LOCK; \
printf("-- ERROR [%.03Lf tid=%ld] -- " _x_msg "\n", now_monotonic_ms(), syscall(SYS_gettid), ##__VA_ARGS__); \
LOGGING_UNLOCK; \
}
#define LOG_PERROR(_x_msg, ...) { \
char _buf[1024] = ""; \
strerror_r(errno, _buf, 1024); \
LOGGING_LOCK; \
printf("-- ERROR [%.03Lf tid=%ld] -- " _x_msg ": %s\n", now_monotonic_ms(), syscall(SYS_gettid), ##__VA_ARGS__, _buf); \
LOGGING_UNLOCK; \
}
#define LOG_INFO(_x_msg, ...) { \
LOGGING_LOCK; \
printf("-- INFO [%.03Lf tid=%ld] -- " _x_msg "\n", now_monotonic_ms(), syscall(SYS_gettid), ##__VA_ARGS__); \
LOGGING_UNLOCK; \
}
#define LOG_INFO_NOLOCK(_x_msg, ...) { \
printf("-- INFO [%.03Lf tid=%ld] -- " _x_msg "\n", now_monotonic_ms(), syscall(SYS_gettid), ##__VA_ARGS__); \
}
#define LOG_PERF(_x_msg, ...) { \
if (log_level >= LOG_LEVEL_PERF) { \
LOGGING_LOCK; \
printf("-- PERF [%.03Lf tid=%ld] -- " _x_msg "\n", now_monotonic_ms(), syscall(SYS_gettid), ##__VA_ARGS__); \
LOGGING_UNLOCK; \
} \
}
#define LOG_VERBOSE(_x_msg, ...) { \
if (log_level >= LOG_LEVEL_VERBOSE) { \
LOGGING_LOCK; \
printf("-- VERB [%.03Lf tid=%ld] -- " _x_msg "\n", now_monotonic_ms(), syscall(SYS_gettid), ##__VA_ARGS__); \
LOGGING_UNLOCK; \
} \
}
#define LOG_DEBUG(_x_msg, ...) { \
if (log_level >= LOG_LEVEL_DEBUG) { \
LOGGING_LOCK; \
printf("-- DEBUG [%.03Lf tid=%ld] -- " _x_msg "\n", now_monotonic_ms(), syscall(SYS_gettid), ##__VA_ARGS__); \
LOGGING_UNLOCK; \
} \
}

View File

@@ -1,295 +0,0 @@
/*****************************************************************************
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include <assert.h>
#ifdef NDEBUG
# error WTF dude? Asserts are good things!
#endif
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <getopt.h>
#include <pthread.h>
#include "config.h"
#include "tools.h"
#include "logging.h"
#include "device.h"
#include "encoder.h"
#include "stream.h"
#include "http.h"
static const char _short_opts[] = "d:x:y:f:a:e:z:tn:w:q:c:s:p:r:h";
static const struct option _long_opts[] = {
{"device", required_argument, NULL, 'd'},
{"width", required_argument, NULL, 'x'},
{"height", required_argument, NULL, 'y'},
{"format", required_argument, NULL, 'f'},
{"tv-standard", required_argument, NULL, 'a'},
{"every-frame", required_argument, NULL, 'e'},
{"min-frame-size", required_argument, NULL, 'z'},
{"dv-timings", no_argument, NULL, 't'},
{"buffers", required_argument, NULL, 'b'},
{"workers", required_argument, NULL, 'w'},
{"quality", required_argument, NULL, 'q'},
{"encoder", required_argument, NULL, 'c'},
# ifdef OMX_ENCODER
{"encoder-omx-use-ijg", required_argument, NULL, 500},
# endif
{"device-timeout", required_argument, NULL, 1000},
{"device-error-timeout", required_argument, NULL, 1001},
{"host", required_argument, NULL, 's'},
{"port", required_argument, NULL, 'p'},
{"drop-same-frames", required_argument, NULL, 'r'},
{"fake-width", required_argument, NULL, 2000},
{"fake-height", required_argument, NULL, 2001},
{"server-timeout", required_argument, NULL, 2002},
{"perf", no_argument, NULL, 5000},
{"verbose", no_argument, NULL, 5001},
{"debug", no_argument, NULL, 5002},
{"log-level", required_argument, NULL, 5010},
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0},
};
static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_server_t *server) {
printf("\nuStreamer - Lightweight and fast MJPG-HTTP streamer\n");
printf("===================================================\n\n");
printf("Version: %s; license: GPLv3\n", VERSION);
printf("Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com>\n\n");
printf("Capturing options:\n");
printf("------------------\n");
printf(" -d|--device </dev/path> -- Path to V4L2 device. Default: %s\n\n", dev->path);
printf(" -x|--width <N> -- Initial image width. Default: %d\n\n", dev->width);
printf(" -y|--height <N> -- Initial image height. Default: %d\n\n", dev->height);
printf(" -f|--format <fmt> -- Image format.\n");
printf(" Available: %s; default: YUYV.\n\n", FORMATS_STR);
printf(" -a|--tv-standard <std> -- Force TV standard.\n");
printf(" Available: %s; default: disabled.\n\n", STANDARDS_STR);
printf(" -e|--every-frame <N> -- Drop all input frames except specified. Default: disabled.\n\n");
printf(" -z|--min-frame-size <N> -- Drop frames smaller then this limit.\n");
printf(" Useful if the device produces small-sized garbage frames.\n\n");
printf(" -t|--dv-timings -- Enable DV timings queriyng and events processing.\n");
printf(" Supports automatic resolution changing. Default: disabled.\n\n");
printf(" -b|--buffers <N> -- The number of buffers to receive data from the device.\n");
printf(" Each buffer may processed using an intermediate thread.\n");
printf(" Default: %d (number of CPU cores + 1)\n\n", dev->n_buffers);
printf(" -w|--workers <N> -- The number of compressing threads. Default: %d (== --buffers).\n\n", dev->n_workers);
printf(" -q|--quality <N> -- Set quality of JPEG encoding from 1 to 100 (best). Default: %d.\n\n", encoder->quality);
printf(" --encoder <type> -- Use specified encoder. It may affects to workers number.\n");
printf(" -- Available: %s; default: CPU.\n\n", ENCODER_TYPES_STR);
# ifdef OMX_ENCODER
printf(" --encoder-omx-use-ijg -- Use the standard IJG quality tables when encoding images using OMX.\n");
printf(" Default: disabled.\n\n");
# endif
printf(" --device-timeout <seconds> -- Timeout for device querying. Default: %d\n\n", dev->timeout);
printf(" --device-error-timeout <seconds> -- Delay before trying to connect to the device again\n");
printf(" after a timeout. Default: %d\n\n", dev->error_timeout);
printf("HTTP server options:\n");
printf("--------------------\n");
printf(" --host <address> -- Listen on Hostname or IP. Default: %s\n\n", server->host);
printf(" --port <N> -- Bind to this TCP port. Default: %d\n\n", server->port);
printf(" --drop-same-frames <N> -- Don't send same frames to clients, but no more than specified number.\n");
printf(" It can significantly reduce the outgoing traffic, but will increase\n");
printf(" the CPU loading. Don't use this option with analog signal sources\n");
printf(" or webcams, it's useless. Default: disabled.\n\n");
printf(" --fake-width <N> -- Override image width for /ping. Default: disabled\n\n");
printf(" --fake-height <N> -- Override image height for /ping. Default: disabled.\n\n");
printf(" --server-timeout <seconds> -- Timeout for client connections. Default: %d\n\n", server->timeout);
printf("Misc options:\n");
printf("-------------\n");
printf(" --log-level <N> -- Verbosity level of messages from 0 (info) to 3 (debug).\n");
printf(" Enabling debugging messages can slow down the program.\n");
printf(" Available levels: 0=info, 1=performance, 2=verbose, 3=debug.\n");
printf(" Default: %d.\n\n", log_level);
printf(" --perf -- Enable performance messages (same as log-level=1). Default: disabled.\n\n");
printf(" --verbose -- Enable verbose messages and lower (same as log-level=2). Default: disabled.\n\n");
printf(" --debug -- Enable debug messages and lower (same as --log-level=3). Default: disabled.\n\n");
printf(" -h|--help -- Print this messages and exit.\n\n");
}
static int _parse_options(int argc, char *argv[], struct device_t *dev, struct encoder_t *encoder, struct http_server_t *server) {
# define OPT_ARG(_dest) \
{ _dest = optarg; break; }
# define OPT_TRUE(_dest) \
{ _dest = true; break; }
# define OPT_UNSIGNED(_dest, _name, _min, _max) \
{ errno = 0; int _tmp = strtol(optarg, NULL, 0); \
if (errno || _tmp < _min || _tmp > _max) \
{ printf("Invalid value for '%s=%u'; min=%u; max=%u\n", _name, _tmp, _min, _max); return -1; } \
_dest = _tmp; break; }
# define OPT_PARSE(_dest, _func, _invalid, _name) \
{ if ((_dest = _func(optarg)) == _invalid) \
{ printf("Unknown " _name ": %s\n", optarg); return -1; } \
break; }
int index;
int ch;
log_level = LOG_LEVEL_INFO;
while ((ch = getopt_long(argc, argv, _short_opts, _long_opts, &index)) >= 0) {
switch (ch) {
case 'd': OPT_ARG(dev->path);
case 'x': OPT_UNSIGNED(dev->width, "--width", 320, 1920);
case 'y': OPT_UNSIGNED(dev->height, "--height", 180, 1200);
# pragma GCC diagnostic ignored "-Wsign-compare"
# pragma GCC diagnostic push
case 'f': OPT_PARSE(dev->format, device_parse_format, FORMAT_UNKNOWN, "pixel format");
# pragma GCC diagnostic pop
case 'a': OPT_PARSE(dev->standard, device_parse_standard, STANDARD_UNKNOWN, "TV standard");
case 'e': OPT_UNSIGNED(dev->every_frame, "--every-frame", 1, 30);
case 'z': OPT_UNSIGNED(dev->min_frame_size, "--min-frame-size", 0, 8192);
case 't': OPT_TRUE(dev->dv_timings);
case 'b': OPT_UNSIGNED(dev->n_buffers, "--buffers", 1, 32);
case 'w': OPT_UNSIGNED(dev->n_workers, "--workers", 1, 32);
case 'q': OPT_UNSIGNED(encoder->quality, "--quality", 1, 100);
case 'c': OPT_PARSE(encoder->type, encoder_parse_type, ENCODER_TYPE_UNKNOWN, "encoder type");
# ifdef OMX_ENCODER
case 500: encoder->omx_use_ijg = true; break;
# endif
case 1000: OPT_UNSIGNED(dev->timeout, "--timeout", 1, 60);
case 1001: OPT_UNSIGNED(dev->error_timeout, "--error-timeout", 1, 60);
case 's': server->host = optarg; break;
case 'p': OPT_UNSIGNED(server->port, "--port", 1, 65535);
case 'r': OPT_UNSIGNED(server->drop_same_frames, "--drop-same-frames", 0, 30);
case 2000: OPT_UNSIGNED(server->fake_width, "--fake-width", 0, 1920);
case 2001: OPT_UNSIGNED(server->fake_height, "--fake-height", 0, 1200);
case 2002: OPT_UNSIGNED(server->timeout, "--server-timeout", 1, 60);
case 5000: log_level = LOG_LEVEL_PERF; break;
case 5001: log_level = LOG_LEVEL_VERBOSE; break;
case 5002: log_level = LOG_LEVEL_DEBUG; break;
case 5010: OPT_UNSIGNED(log_level, "--log-level", 0, 3);
case 0: break;
case 'h': default: _help(dev, encoder, server); return -1;
}
}
# undef OPT_PARSE
# undef OPT_UNSIGNED
# undef OPT_TRUE
# undef OPT_ARG
return 0;
}
struct main_context_t {
struct stream_t *stream;
struct http_server_t *server;
};
static struct main_context_t *_ctx;
static void _block_thread_signals() {
sigset_t mask;
assert(!sigemptyset(&mask));
assert(!sigaddset(&mask, SIGINT));
assert(!sigaddset(&mask, SIGTERM));
assert(!pthread_sigmask(SIG_BLOCK, &mask, NULL));
}
static void *_stream_loop_thread(UNUSED void *arg) {
_block_thread_signals();
stream_loop(_ctx->stream);
return NULL;
}
static void *_server_loop_thread(UNUSED void *arg) {
_block_thread_signals();
http_server_loop(_ctx->server);
return NULL;
}
static void _signal_handler(int signum) {
LOG_INFO_NOLOCK("===== Stopping by %s =====", (signum == SIGTERM ? "SIGTERM" : "SIGINT"));
stream_loop_break(_ctx->stream);
http_server_loop_break(_ctx->server);
}
static void _install_signal_handlers() {
struct sigaction sig_act;
MEMSET_ZERO(sig_act);
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));
LOG_INFO("Installing SIGINT handler ...");
assert(!sigaction(SIGINT, &sig_act, NULL));
LOG_INFO("Installing SIGTERM handler ...");
assert(!sigaction(SIGTERM, &sig_act, NULL));
LOG_INFO("Ignoring SIGPIPE ...");
assert(signal(SIGPIPE, SIG_IGN) != SIG_ERR);
}
int main(int argc, char *argv[]) {
struct device_t *dev;
struct encoder_t *encoder;
struct stream_t *stream;
struct http_server_t *server;
int exit_code = 0;
LOGGING_INIT;
dev = device_init();
encoder = encoder_init();
stream = stream_init(dev, encoder);
server = http_server_init(stream);
if ((exit_code = _parse_options(argc, argv, dev, encoder, server)) == 0) {
_install_signal_handlers();
encoder_prepare(encoder);
pthread_t stream_loop_tid;
pthread_t server_loop_tid;
struct main_context_t ctx;
ctx.stream = stream;
ctx.server = server;
_ctx = &ctx;
if ((exit_code = http_server_listen(server)) == 0) {
A_PTHREAD_CREATE(&stream_loop_tid, _stream_loop_thread, NULL);
A_PTHREAD_CREATE(&server_loop_tid, _server_loop_thread, NULL);
A_PTHREAD_JOIN(stream_loop_tid);
A_PTHREAD_JOIN(server_loop_tid);
}
}
http_server_destroy(server);
stream_destroy(stream);
encoder_destroy(encoder);
device_destroy(dev);
LOGGING_DESTROY;
return abs(exit_code);
}

View File

@@ -1,155 +0,0 @@
/*****************************************************************************
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include <unistd.h>
#include <IL/OMX_Core.h>
#include <IL/OMX_Component.h>
#include "../logging.h"
#include "formatters.h"
#include "component.h"
static int _component_wait_port_changed(OMX_HANDLETYPE *component, const OMX_U32 port, const OMX_BOOL enabled);
static int _component_wait_state_changed(OMX_HANDLETYPE *component, const OMX_STATETYPE wanted);
int component_enable_port(OMX_HANDLETYPE *component, const OMX_U32 port) {
OMX_ERRORTYPE error;
LOG_DEBUG("Enabling OMX port %u ...", port);
if ((error = OMX_SendCommand(*component, OMX_CommandPortEnable, port, NULL)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't enable OMX port %u", port);
return -1;
}
return _component_wait_port_changed(component, port, OMX_TRUE);
}
int component_disable_port(OMX_HANDLETYPE *component, const OMX_U32 port) {
OMX_ERRORTYPE error;
LOG_DEBUG("Disabling OMX port %u ...", port);
if ((error = OMX_SendCommand(*component, OMX_CommandPortDisable, port, NULL)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't disable OMX port %u", port);
return -1;
}
return _component_wait_port_changed(component, port, OMX_FALSE);
}
int component_get_portdef(OMX_HANDLETYPE *component, OMX_PARAM_PORTDEFINITIONTYPE *portdef, const OMX_U32 port) {
OMX_ERRORTYPE error;
OMX_INIT_STRUCTURE(*portdef);
portdef->nPortIndex = port;
LOG_DEBUG("Fetching OMX port %u definition ...", port);
if ((error = OMX_GetParameter(*component, OMX_IndexParamPortDefinition, portdef)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't get OMX port %u definition", port);
return -1;
}
return 0;
}
int component_set_portdef(OMX_HANDLETYPE *component, OMX_PARAM_PORTDEFINITIONTYPE *portdef) {
OMX_ERRORTYPE error;
LOG_DEBUG("Writing OMX port %u definition ...", portdef->nPortIndex);
if ((error = OMX_SetParameter(*component, OMX_IndexParamPortDefinition, portdef)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't set OMX port %u definition", portdef->nPortIndex);
return -1;
}
return 0;
}
int component_set_state(OMX_HANDLETYPE *component, const OMX_STATETYPE state) {
const char *const state_str = omx_state_to_string(state);
OMX_ERRORTYPE error;
int retries = 50;
LOG_DEBUG("Switching component state to %s ...", state_str);
do {
error = OMX_SendCommand(*component, OMX_CommandStateSet, state, NULL);
if (error == OMX_ErrorNone) {
return _component_wait_state_changed(component, state);
} else if (error == OMX_ErrorInsufficientResources && retries) {
// Иногда железо не инициализируется, хз почему, просто ретраим, со второй попытки сработает
LOG_OMX_ERROR(error, "Can't switch OMX component state to %s, need to retry", state_str);
retries -= 1;
usleep(8000);
} else {
break;
}
} while (retries);
LOG_OMX_ERROR(error, "Can't switch OMX component state to %s", state_str);
return -1;
}
static int _component_wait_port_changed(OMX_HANDLETYPE *component, const OMX_U32 port, const OMX_BOOL enabled) {
OMX_ERRORTYPE error;
OMX_PARAM_PORTDEFINITIONTYPE portdef;
int retries = 50;
OMX_INIT_STRUCTURE(portdef);
portdef.nPortIndex = port;
do {
if ((error = OMX_GetParameter(*component, OMX_IndexParamPortDefinition, &portdef)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't get OMX port %u definition for waiting", port);
return -1;
}
if (portdef.bEnabled != enabled) {
LOG_DEBUG("Waiting for OMX %s port %u", (enabled ? "enabling" : "disabling"), port);
retries -= 1;
usleep(8000);
}
} while (portdef.bEnabled != enabled && retries);
LOG_DEBUG("OMX port %u %s", port, (enabled ? "enabled" : "disabled"));
return (portdef.bEnabled == enabled ? 0 : -1);
}
static int _component_wait_state_changed(OMX_HANDLETYPE *component, const OMX_STATETYPE wanted) {
OMX_ERRORTYPE error;
OMX_STATETYPE state;
int retries = 50;
do {
if ((error = OMX_GetState(*component, &state)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Failed to get OMX component state");
return -1;
}
if (state != wanted) {
LOG_DEBUG("Waiting when OMX component state changes to %s", omx_state_to_string(wanted));
retries -= 1;
usleep(8000);
}
} while (state != wanted && retries);
LOG_DEBUG("Switched OMX component state to %s", omx_state_to_string(wanted))
return (state == wanted ? 0 : -1);
}

View File

@@ -1,464 +0,0 @@
/*****************************************************************************
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <linux/videodev2.h>
#include <bcm_host.h>
#include <IL/OMX_Core.h>
#include <IL/OMX_Component.h>
#include <IL/OMX_Broadcom.h>
#include <interface/vcos/vcos_semaphore.h>
#include "../logging.h"
#include "../tools.h"
#include "../device.h"
#include "formatters.h"
#include "component.h"
#include "encoder.h"
#define INPUT_PORT 340
#define OUTPUT_PORT 341
static int _omx_init_component(struct omx_encoder_t *omx);
static int _omx_init_disable_ports(struct omx_encoder_t *omx);
static int _omx_setup_input(struct omx_encoder_t *omx, struct device_t *dev);
static int _omx_setup_output(struct omx_encoder_t *omx, const unsigned quality, const bool use_ijg);
static int _omx_encoder_clear_ports(struct omx_encoder_t *omx);
static OMX_ERRORTYPE _omx_event_handler(UNUSED OMX_HANDLETYPE encoder,
OMX_PTR v_omx, OMX_EVENTTYPE event, OMX_U32 data1,
UNUSED OMX_U32 data2, UNUSED OMX_PTR event_data);
static OMX_ERRORTYPE _omx_input_required_handler(UNUSED OMX_HANDLETYPE encoder,
OMX_PTR v_omx, UNUSED OMX_BUFFERHEADERTYPE *buffer);
static OMX_ERRORTYPE _omx_output_available_handler(UNUSED OMX_HANDLETYPE encoder,
OMX_PTR v_omx, UNUSED OMX_BUFFERHEADERTYPE *buffer);
struct omx_encoder_t *omx_encoder_init() {
// Some theory:
// - http://www.fourcc.org/yuv.php
// - https://kwasi-ich.de/blog/2017/11/26/omx/
// - https://github.com/hopkinskong/rpi-omx-jpeg-encode/blob/master/jpeg_bench.cpp
// - https://github.com/kwasmich/OMXPlayground/blob/master/omxJPEGEnc.c
// - https://github.com/gagle/raspberrypi-openmax-jpeg/blob/master/jpeg.c
// - https://www.raspberrypi.org/forums/viewtopic.php?t=154790
// - https://bitbucket.org/bensch128/omxjpegencode/src/master/jpeg_encoder.cpp
struct omx_encoder_t *omx;
OMX_ERRORTYPE error;
A_CALLOC(omx, 1);
LOG_INFO("Initializing OMX JPEG encoder ...");
LOG_DEBUG("Initializing BCM ...");
bcm_host_init();
LOG_DEBUG("Initializing OMX ...");
if ((error = OMX_Init()) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't initialize OMX");
goto error;
}
omx->i_omx = true;
if (vcos_semaphore_create(&omx->handler_lock, "handler_lock", 0) != VCOS_SUCCESS) {
LOG_ERROR("Can't create VCOS semaphore");
goto error;
}
omx->i_handler_lock = true;
if (_omx_init_component(omx) < 0) {
goto error;
}
if (_omx_init_disable_ports(omx) < 0) {
goto error;
}
return omx;
error:
omx_encoder_destroy(omx);
return NULL;
}
void omx_encoder_destroy(struct omx_encoder_t *omx) {
OMX_ERRORTYPE error;
LOG_INFO("Destroying OMX JPEG encoder ...");
component_set_state(&omx->encoder, OMX_StateIdle);
_omx_encoder_clear_ports(omx);
component_set_state(&omx->encoder, OMX_StateLoaded);
if (omx->i_handler_lock) {
vcos_semaphore_delete(&omx->handler_lock);
}
if (omx->i_encoder) {
if ((error = OMX_FreeHandle(omx->encoder)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't free OMX.broadcom.image_encode");
}
}
if (omx->i_omx) {
OMX_Deinit();
}
bcm_host_deinit();
free(omx);
}
int omx_encoder_prepare_for_device(struct omx_encoder_t *omx, struct device_t *dev, const unsigned quality, const bool use_ijg) {
if (component_set_state(&omx->encoder, OMX_StateIdle) < 0) {
return -1;
}
if (_omx_encoder_clear_ports(omx) < 0) {
return -1;
}
if (_omx_setup_input(omx, dev) < 0) {
return -1;
}
if (_omx_setup_output(omx, quality, use_ijg) < 0) {
return -1;
}
if (component_set_state(&omx->encoder, OMX_StateExecuting) < 0) {
return -1;
}
return 0;
}
int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev, const unsigned index) {
OMX_ERRORTYPE error;
bool loaded = false;
if ((error = OMX_FillThisBuffer(omx->encoder, omx->output_buffer)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Failed to request filling of the output buffer on encoder");
return -1;
}
dev->run->pictures[index].size = 0;
omx->output_available = false;
omx->input_required = true;
while (true) {
if (omx->failed) {
return -1;
}
if (omx->output_available) {
omx->output_available = false;
memcpy(
dev->run->pictures[index].data + dev->run->pictures[index].size,
omx->output_buffer->pBuffer,
omx->output_buffer->nFilledLen
);
assert(dev->run->pictures[index].size + omx->output_buffer->nFilledLen <= dev->run->max_picture_size);
dev->run->pictures[index].size += omx->output_buffer->nFilledLen;
if (omx->output_buffer->nFlags & OMX_BUFFERFLAG_ENDOFFRAME) {
omx->output_buffer->nFlags = 0;
break;
}
if ((error = OMX_FillThisBuffer(omx->encoder, omx->output_buffer)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Failed to request filling of the output buffer on encoder");
return -1;
}
}
if (omx->input_required) {
omx->input_required = false;
if (loaded) {
continue;
}
loaded = true;
memcpy(omx->input_buffer->pBuffer, dev->run->hw_buffers[index].start, dev->run->hw_buffers[index].length);
omx->input_buffer->nOffset = 0;
omx->input_buffer->nFilledLen = dev->run->hw_buffers[index].length;
if ((error = OMX_EmptyThisBuffer(omx->encoder, omx->input_buffer)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Failed to request emptying of the input buffer on encoder");
return -1;
}
}
vcos_semaphore_wait(&omx->handler_lock);
}
return 0;
}
static int _omx_init_component(struct omx_encoder_t *omx) {
// http://home.nouwen.name/RaspberryPi/documentation/ilcomponents/image_encode.html
OMX_ERRORTYPE error;
OMX_CALLBACKTYPE callbacks;
MEMSET_ZERO(callbacks);
callbacks.EventHandler = _omx_event_handler;
callbacks.EmptyBufferDone = _omx_input_required_handler;
callbacks.FillBufferDone = _omx_output_available_handler;
LOG_DEBUG("Initializing OMX.broadcom.image_encode ...");
if ((error = OMX_GetHandle(&omx->encoder, "OMX.broadcom.image_encode", omx, &callbacks)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't initialize OMX.broadcom.image_encode");
return -1;
}
omx->i_encoder = true;
return 0;
}
static int _omx_init_disable_ports(struct omx_encoder_t *omx) {
OMX_ERRORTYPE error;
OMX_INDEXTYPE types[] = {
OMX_IndexParamAudioInit, OMX_IndexParamVideoInit,
OMX_IndexParamImageInit, OMX_IndexParamOtherInit,
};
OMX_PORT_PARAM_TYPE ports;
OMX_INIT_STRUCTURE(ports);
if ((error = OMX_GetParameter(omx->encoder, OMX_IndexParamImageInit, &ports)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't OMX_GetParameter(OMX_IndexParamImageInit)");
return -1;
}
for (unsigned index = 0; index < 4; ++index) {
if ((error = OMX_GetParameter(omx->encoder, types[index], &ports)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't OMX_GetParameter(types[%u])", index);
return -1;
}
for (OMX_U32 port = ports.nStartPortNumber; port < ports.nStartPortNumber + ports.nPorts; ++port) {
if (component_disable_port(&omx->encoder, port) < 0) {
return -1;
}
}
}
return 0;
}
static int _omx_setup_input(struct omx_encoder_t *omx, struct device_t *dev) {
OMX_ERRORTYPE error;
OMX_PARAM_PORTDEFINITIONTYPE portdef;
LOG_DEBUG("Setting up OMX JPEG input port ...");
if (component_get_portdef(&omx->encoder, &portdef, INPUT_PORT) < 0) {
LOG_ERROR("... first");
return -1;
}
portdef.format.image.nFrameWidth = dev->run->width;
portdef.format.image.nFrameHeight = dev->run->height;
portdef.format.image.nStride = 0;
# define ALIGN(_x, _y) (((_x) + ((_y) - 1)) & ~((_y) - 1))
portdef.format.image.nSliceHeight = ALIGN(dev->run->height, 16);
# undef ALIGN
portdef.format.image.bFlagErrorConcealment = OMX_FALSE;
portdef.format.image.eCompressionFormat = OMX_IMAGE_CodingUnused;
portdef.nBufferSize = dev->run->max_picture_size;
switch (dev->run->format) {
// https://www.fourcc.org/yuv.php
// Also see comments inside OMX_IVCommon.h
case V4L2_PIX_FMT_YUYV: portdef.format.image.eColorFormat = OMX_COLOR_FormatYCbYCr; break;
case V4L2_PIX_FMT_UYVY: portdef.format.image.eColorFormat = OMX_COLOR_FormatCbYCrY; break;
case V4L2_PIX_FMT_RGB565: portdef.format.image.eColorFormat = OMX_COLOR_Format16bitRGB565; break;
// TODO: Check RGB565 + OMX. I don't have any USB devices which supports it.
default: assert(0 && "Unsupported input format for OMX JPEG compressor");
}
if (component_set_portdef(&omx->encoder, &portdef) < 0) {
return -1;
}
if (component_get_portdef(&omx->encoder, &portdef, INPUT_PORT) < 0) {
LOG_ERROR("... second");
return -1;
}
if (component_enable_port(&omx->encoder, INPUT_PORT) < 0) {
return -1;
}
omx->i_input_port_enabled = true;
if ((error = OMX_AllocateBuffer(omx->encoder, &omx->input_buffer, INPUT_PORT, NULL, portdef.nBufferSize)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't allocate OMX JPEG input buffer");
return -1;
}
return 0;
}
static int _omx_setup_output(struct omx_encoder_t *omx, const unsigned quality, const bool use_ijg) {
OMX_ERRORTYPE error;
OMX_PARAM_PORTDEFINITIONTYPE portdef;
LOG_DEBUG("Setting up OMX JPEG output port ...");
if (component_get_portdef(&omx->encoder, &portdef, OUTPUT_PORT) < 0) {
LOG_ERROR("... first");
return -1;
}
portdef.format.image.bFlagErrorConcealment = OMX_FALSE;
portdef.format.image.eCompressionFormat = OMX_IMAGE_CodingJPEG;
portdef.format.image.eColorFormat = OMX_COLOR_FormatYCbYCr;
if (component_set_portdef(&omx->encoder, &portdef) < 0) {
return -1;
}
if (component_get_portdef(&omx->encoder, &portdef, OUTPUT_PORT) < 0) {
LOG_ERROR("... second");
return -1;
}
{
OMX_CONFIG_BOOLEANTYPE exif;
OMX_INIT_STRUCTURE(exif);
exif.bEnabled = OMX_FALSE;
if ((error = OMX_SetParameter(omx->encoder, OMX_IndexParamBrcmDisableEXIF, &exif)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't disable EXIF on OMX JPEG");
return -1;
}
}
if (use_ijg) {
OMX_PARAM_IJGSCALINGTYPE ijg;
OMX_INIT_STRUCTURE(ijg);
ijg.nPortIndex = OUTPUT_PORT;
ijg.bEnabled = OMX_TRUE;
if ((error = OMX_SetParameter(omx->encoder, OMX_IndexParamBrcmEnableIJGTableScaling, &ijg)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't set OMX JPEG IJG settings");
return -1;
}
}
{
OMX_IMAGE_PARAM_QFACTORTYPE qfactor;
OMX_INIT_STRUCTURE(qfactor);
qfactor.nPortIndex = OUTPUT_PORT;
qfactor.nQFactor = quality;
if ((error = OMX_SetParameter(omx->encoder, OMX_IndexParamQFactor, &qfactor)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't set OMX JPEG quality");
return -1;
}
}
if (component_enable_port(&omx->encoder, OUTPUT_PORT) < 0) {
return -1;
}
omx->i_output_port_enabled = true;
if ((error = OMX_AllocateBuffer(omx->encoder, &omx->output_buffer, OUTPUT_PORT, NULL, portdef.nBufferSize)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't allocate OMX JPEG output buffer");
return -1;
}
return 0;
}
static int _omx_encoder_clear_ports(struct omx_encoder_t *omx) {
OMX_ERRORTYPE error;
int retcode = 0;
if (omx->i_output_port_enabled) {
retcode -= component_disable_port(&omx->encoder, OUTPUT_PORT);
omx->i_output_port_enabled = false;
}
if (omx->i_input_port_enabled) {
retcode -= component_disable_port(&omx->encoder, INPUT_PORT);
omx->i_input_port_enabled = false;
}
if (omx->input_buffer) {
if ((error = OMX_FreeBuffer(omx->encoder, INPUT_PORT, omx->input_buffer)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't free OMX JPEG input buffer");
// retcode -= 1;
}
omx->input_buffer = NULL;
}
if (omx->output_buffer) {
if ((error = OMX_FreeBuffer(omx->encoder, OUTPUT_PORT, omx->output_buffer)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't free OMX JPEG output buffer");
// retcode -= 1;
}
omx->output_buffer = NULL;
}
return retcode;
}
static OMX_ERRORTYPE _omx_event_handler(UNUSED OMX_HANDLETYPE encoder,
OMX_PTR v_omx, OMX_EVENTTYPE event, OMX_U32 data1,
UNUSED OMX_U32 data2, UNUSED OMX_PTR event_data) {
// OMX calls this handler for all the events it emits
struct omx_encoder_t *omx = (struct omx_encoder_t *)v_omx;
if (event == OMX_EventError) {
LOG_OMX_ERROR((OMX_ERRORTYPE)data1, "OMX error event received");
omx->failed = true;
vcos_semaphore_post(&omx->handler_lock);
}
return OMX_ErrorNone;
}
static OMX_ERRORTYPE _omx_input_required_handler(UNUSED OMX_HANDLETYPE encoder,
OMX_PTR v_omx, UNUSED OMX_BUFFERHEADERTYPE *buffer) {
// Called by OMX when the encoder component requires
// the input buffer to be filled with RAW image data
struct omx_encoder_t *omx = (struct omx_encoder_t *)v_omx;
omx->input_required = true;
vcos_semaphore_post(&omx->handler_lock);
return OMX_ErrorNone;
}
static OMX_ERRORTYPE _omx_output_available_handler(UNUSED OMX_HANDLETYPE encoder,
OMX_PTR v_omx, UNUSED OMX_BUFFERHEADERTYPE *buffer) {
// Called by OMX when the encoder component has filled
// the output buffer with JPEG data
struct omx_encoder_t *omx = (struct omx_encoder_t *)v_omx;
omx->output_available = true;
vcos_semaphore_post(&omx->handler_lock);
return OMX_ErrorNone;
}

View File

@@ -1,82 +0,0 @@
/*****************************************************************************
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include <stdio.h>
#include <assert.h>
#include <IL/OMX_IVCommon.h>
#include <IL/OMX_Core.h>
#include "../tools.h"
#include "formatters.h"
#define CASE_TO_STRING(_val) \
case _val: { return #_val; }
#define CASE_ASSERT(_msg, _val) default: { \
char *_buf; A_CALLOC(_buf, 128); \
sprintf(_buf, _msg ": 0x%08x", _val); \
assert(0 && _buf); \
}
const char *omx_error_to_string(const OMX_ERRORTYPE error) {
switch (error) {
CASE_TO_STRING(OMX_ErrorNone);
CASE_TO_STRING(OMX_ErrorInsufficientResources);
CASE_TO_STRING(OMX_ErrorUndefined);
CASE_TO_STRING(OMX_ErrorInvalidComponentName);
CASE_TO_STRING(OMX_ErrorComponentNotFound);
CASE_TO_STRING(OMX_ErrorInvalidComponent);
CASE_TO_STRING(OMX_ErrorBadParameter);
CASE_TO_STRING(OMX_ErrorNotImplemented);
CASE_TO_STRING(OMX_ErrorUnderflow);
CASE_TO_STRING(OMX_ErrorOverflow);
CASE_TO_STRING(OMX_ErrorHardware);
CASE_TO_STRING(OMX_ErrorInvalidState);
CASE_TO_STRING(OMX_ErrorStreamCorrupt);
CASE_TO_STRING(OMX_ErrorPortsNotCompatible);
CASE_TO_STRING(OMX_ErrorResourcesLost);
CASE_TO_STRING(OMX_ErrorNoMore);
CASE_TO_STRING(OMX_ErrorVersionMismatch);
CASE_TO_STRING(OMX_ErrorNotReady);
CASE_TO_STRING(OMX_ErrorTimeout);
CASE_TO_STRING(OMX_ErrorSameState);
CASE_TO_STRING(OMX_ErrorResourcesPreempted);
CASE_TO_STRING(OMX_ErrorPortUnresponsiveDuringAllocation);
CASE_TO_STRING(OMX_ErrorPortUnresponsiveDuringDeallocation);
CASE_TO_STRING(OMX_ErrorPortUnresponsiveDuringStop);
CASE_TO_STRING(OMX_ErrorIncorrectStateTransition);
default: return "Unknown OMX error";
}
}
const char *omx_state_to_string(const OMX_STATETYPE state) {
switch (state) {
CASE_TO_STRING(OMX_StateLoaded);
CASE_TO_STRING(OMX_StateIdle);
CASE_TO_STRING(OMX_StateExecuting);
CASE_ASSERT("Unsupported OMX state", state);
}
}
#undef CASE_TO_STRING
#undef CASE_ASSERT

View File

@@ -1,536 +0,0 @@
/*****************************************************************************
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include <errno.h>
#include <time.h>
#include <assert.h>
#include <pthread.h>
#include <sys/select.h>
#include <linux/videodev2.h>
#include "tools.h"
#include "logging.h"
#include "xioctl.h"
#include "device.h"
#include "encoder.h"
#include "stream.h"
#include "jpeg/encoder.h"
static long double _stream_get_fluency_delay(struct device_t *dev, struct workers_pool_t *pool);
static void _stream_expose_picture(struct stream_t *stream, unsigned buf_index);
static int _stream_init_loop(struct device_t *dev, struct workers_pool_t *pool);
static int _stream_init(struct device_t *dev, struct workers_pool_t *pool);
static void _stream_init_workers(struct device_t *dev, struct workers_pool_t *pool);
static void *_stream_worker_thread(void *v_ctx);
static void _stream_destroy_workers(struct device_t *dev, struct workers_pool_t *pool);
static int _stream_control(struct device_t *dev, const bool enable);
static int _stream_grab_buffer(struct device_t *dev, struct v4l2_buffer *buf_info);
static int _stream_release_buffer(struct device_t *dev, struct v4l2_buffer *buf_info);
static int _stream_handle_event(struct device_t *dev);
struct stream_t *stream_init(struct device_t *dev, struct encoder_t *encoder) {
struct stream_t *stream;
A_CALLOC(stream, 1);
stream->dev = dev;
stream->encoder = encoder;
A_PTHREAD_M_INIT(&stream->mutex);
return stream;
}
void stream_destroy(struct stream_t *stream) {
A_PTHREAD_M_DESTROY(&stream->mutex);
free(stream);
}
void stream_loop(struct stream_t *stream) {
struct workers_pool_t pool;
bool workers_stop;
MEMSET_ZERO(pool);
pool.encoder = stream->encoder;
pool.workers_stop = &workers_stop;
LOG_INFO("Using V4L2 device: %s", stream->dev->path);
while (_stream_init_loop(stream->dev, &pool) == 0) {
struct worker_t *oldest_worker = NULL;
struct worker_t *last_worker = NULL;
unsigned frames_count = 0;
long double grab_after = 0;
unsigned fluency_passed = 0;
unsigned fps = 0;
long long fps_second = 0;
LOG_DEBUG("Allocation memory for stream picture ...");
A_CALLOC(stream->picture.data, stream->dev->run->max_picture_size);
while (!stream->dev->stop) {
int free_worker_number = -1;
SEP_DEBUG('-');
LOG_DEBUG("Waiting for workers ...");
A_PTHREAD_M_LOCK(&pool.free_workers_mutex);
A_PTHREAD_C_WAIT_TRUE(pool.free_workers, &pool.free_workers_cond, &pool.free_workers_mutex);
A_PTHREAD_M_UNLOCK(&pool.free_workers_mutex);
if (oldest_worker && !oldest_worker->has_job && oldest_worker->ctx.buf_index >= 0) {
if (oldest_worker->job_failed) {
break;
}
_stream_expose_picture(stream, oldest_worker->ctx.buf_index);
free_worker_number = oldest_worker->ctx.number;
oldest_worker = oldest_worker->order_next;
LOG_PERF("##### ACCEPT : %u", free_worker_number);
} else {
for (unsigned number = 0; number < stream->dev->run->n_workers; ++number) {
if (!pool.workers[number].has_job && (free_worker_number == -1
|| pool.workers[free_worker_number].job_start_time < pool.workers[number].job_start_time
)) {
free_worker_number = number;
break;
}
}
assert(free_worker_number >= 0);
assert(!pool.workers[free_worker_number].has_job);
LOG_PERF("----- DROP : %u", free_worker_number);
}
if (stream->dev->stop) {
break;
}
# define INIT_FD_SET(_set) \
fd_set _set; FD_ZERO(&_set); FD_SET(stream->dev->run->fd, &_set);
INIT_FD_SET(read_fds);
INIT_FD_SET(write_fds);
INIT_FD_SET(error_fds);
# undef INIT_FD_SET
struct timeval timeout;
timeout.tv_sec = stream->dev->timeout;
timeout.tv_usec = 0;
LOG_DEBUG("Calling select() on video device ...");
int retval = select(stream->dev->run->fd + 1, &read_fds, &write_fds, &error_fds, &timeout);
LOG_DEBUG("Device select() --> %d", retval);
if (retval < 0) {
if (errno != EINTR) {
LOG_PERROR("Mainloop select() error");
break;
}
} else if (retval == 0) {
LOG_ERROR("Mainloop select() timeout");
break;
} else {
if (FD_ISSET(stream->dev->run->fd, &read_fds)) {
LOG_DEBUG("Frame is ready");
struct v4l2_buffer buf_info;
if (_stream_grab_buffer(stream->dev, &buf_info) < 0) {
break;
}
if (stream->dev->every_frame) {
if (frames_count < stream->dev->every_frame - 1) {
frames_count += 1;
LOG_DEBUG("Dropping frame %d for option --every-frame=%d", frames_count, stream->dev->every_frame);
goto pass_frame;
}
frames_count = 0;
}
// Workaround for broken, corrupted frames:
// Under low light conditions corrupted frames may get captured.
// The good thing is such frames are quite small compared to the regular pictures.
// For example a VGA (640x480) webcam picture is normally >= 8kByte large,
// corrupted frames are smaller.
if (buf_info.bytesused < stream->dev->min_frame_size) {
LOG_DEBUG("Dropping too small frame sized %d bytes, assuming it as broken", buf_info.bytesused);
goto pass_frame;
}
{
long double now = now_monotonic_ms();
if (now < grab_after) {
fluency_passed += 1;
LOG_VERBOSE("Passed %u frames for fluency: now=%.03Lf; grab_after=%.03Lf", fluency_passed, now, grab_after);
goto pass_frame;
}
fluency_passed = 0;
if ((long long)now != fps_second) {
LOG_PERF("Oldest worker complete, encoding FPS = %u", fps);
stream->fps = fps;
fps = 0;
fps_second = (long long)now;
}
fps += 1;
long double fluency_delay = _stream_get_fluency_delay(stream->dev, &pool);
grab_after = now + fluency_delay;
LOG_VERBOSE("Fluency: delay=%.03Lf; grab_after=%.03Lf", fluency_delay, grab_after);
}
LOG_DEBUG("Grabbed a new frame to buffer %d", buf_info.index);
pool.workers[free_worker_number].ctx.buf_info = buf_info;
if (!oldest_worker) {
oldest_worker = &pool.workers[free_worker_number];
last_worker = oldest_worker;
} else {
if (pool.workers[free_worker_number].order_next) {
pool.workers[free_worker_number].order_next->order_prev = pool.workers[free_worker_number].order_prev;
}
if (pool.workers[free_worker_number].order_prev) {
pool.workers[free_worker_number].order_prev->order_next = pool.workers[free_worker_number].order_next;
}
pool.workers[free_worker_number].order_prev = last_worker;
last_worker->order_next = &pool.workers[free_worker_number];
last_worker = &pool.workers[free_worker_number];
}
last_worker->order_next = NULL;
A_PTHREAD_M_LOCK(&pool.workers[free_worker_number].has_job_mutex);
pool.workers[free_worker_number].ctx.buf_index = buf_info.index;
pool.workers[free_worker_number].has_job = true;
A_PTHREAD_M_UNLOCK(&pool.workers[free_worker_number].has_job_mutex);
A_PTHREAD_C_SIGNAL(&pool.workers[free_worker_number].has_job_cond);
A_PTHREAD_M_LOCK(&pool.free_workers_mutex);
pool.free_workers -= 1;
A_PTHREAD_M_UNLOCK(&pool.free_workers_mutex);
goto next_handlers; // Поток сам освободит буфер
pass_frame:
if (_stream_release_buffer(stream->dev, &buf_info) < 0) {
break;
}
}
next_handlers:
if (FD_ISSET(stream->dev->run->fd, &write_fds)) {
LOG_ERROR("Got unexpected writing event, seems device was disconnected");
break;
}
if (FD_ISSET(stream->dev->run->fd, &error_fds)) {
LOG_INFO("Got V4L2 event");
if (_stream_handle_event(stream->dev) < 0) {
break;
}
}
}
}
A_PTHREAD_M_LOCK(&stream->mutex);
stream->picture.size = 0; // On stream offline
free(stream->picture.data);
stream->width = 0;
stream->height = 0;
stream->updated = true;
A_PTHREAD_M_UNLOCK(&stream->mutex);
}
_stream_destroy_workers(stream->dev, &pool);
_stream_control(stream->dev, false);
device_close(stream->dev);
}
void stream_loop_break(struct stream_t *stream) {
stream->dev->stop = 1;
}
static void _stream_expose_picture(struct stream_t *stream, unsigned buf_index) {
A_PTHREAD_M_LOCK(&stream->mutex);
stream->picture.size = stream->dev->run->pictures[buf_index].size;
stream->picture.allocated = stream->dev->run->pictures[buf_index].allocated;
memcpy(
stream->picture.data, stream->dev->run->pictures[buf_index].data,
stream->picture.size * sizeof(*stream->picture.data)
);
stream->width = stream->dev->run->width;
stream->height = stream->dev->run->height;
stream->updated = true;
A_PTHREAD_M_UNLOCK(&stream->mutex);
}
static long double _stream_get_fluency_delay(struct device_t *dev, struct workers_pool_t *pool) {
long double delay = 0;
for (unsigned number = 0; number < dev->run->n_workers; ++number) {
A_PTHREAD_M_LOCK(&pool->workers[number].last_comp_time_mutex);
if (pool->workers[number].last_comp_time > 0) {
delay += pool->workers[number].last_comp_time;
}
A_PTHREAD_M_UNLOCK(&pool->workers[number].last_comp_time_mutex);
}
// Среднее арифметическое деленное на количество воркеров
return delay / dev->run->n_workers / dev->run->n_workers;
}
static int _stream_init_loop(struct device_t *dev, struct workers_pool_t *pool) {
int retval = -1;
LOG_DEBUG("%s: *dev->stop = %d", __FUNCTION__, dev->stop);
while (!dev->stop) {
if ((retval = _stream_init(dev, pool)) < 0) {
LOG_INFO("Sleeping %d seconds before new stream init ...", dev->error_timeout);
sleep(dev->error_timeout);
} else {
break;
}
}
return retval;
}
static int _stream_init(struct device_t *dev, struct workers_pool_t *pool) {
SEP_INFO('=');
_stream_destroy_workers(dev, pool);
_stream_control(dev, false);
device_close(dev);
if (device_open(dev) < 0) {
goto error;
}
if (_stream_control(dev, true) < 0) {
goto error;
}
encoder_prepare_for_device(pool->encoder, dev);
_stream_init_workers(dev, pool);
return 0;
error:
device_close(dev);
return -1;
}
static void _stream_init_workers(struct device_t *dev, struct workers_pool_t *pool) {
LOG_INFO("Spawning %d workers ...", dev->run->n_workers);
*pool->workers_stop = false;
A_CALLOC(pool->workers, dev->run->n_workers);
A_PTHREAD_M_INIT(&pool->free_workers_mutex);
A_PTHREAD_C_INIT(&pool->free_workers_cond);
for (unsigned number = 0; number < dev->run->n_workers; ++number) {
pool->free_workers += 1;
A_PTHREAD_M_INIT(&pool->workers[number].has_job_mutex);
A_PTHREAD_C_INIT(&pool->workers[number].has_job_cond);
pool->workers[number].ctx.number = number;
pool->workers[number].ctx.dev = dev;
pool->workers[number].ctx.dev_stop = (sig_atomic_t *volatile)&dev->stop;
pool->workers[number].ctx.workers_stop = pool->workers_stop;
pool->workers[number].ctx.encoder = pool->encoder;
pool->workers[number].ctx.last_comp_time_mutex = &pool->workers[number].last_comp_time_mutex;
pool->workers[number].ctx.last_comp_time = &pool->workers[number].last_comp_time;
pool->workers[number].ctx.has_job_mutex = &pool->workers[number].has_job_mutex;
pool->workers[number].ctx.has_job = &pool->workers[number].has_job;
pool->workers[number].ctx.job_failed = &pool->workers[number].job_failed;
pool->workers[number].ctx.job_start_time = &pool->workers[number].job_start_time;
pool->workers[number].ctx.has_job_cond = &pool->workers[number].has_job_cond;
pool->workers[number].ctx.free_workers_mutex = &pool->free_workers_mutex;
pool->workers[number].ctx.free_workers = &pool->free_workers;
pool->workers[number].ctx.free_workers_cond = &pool->free_workers_cond;
A_PTHREAD_CREATE(&pool->workers[number].tid, _stream_worker_thread, (void *)&pool->workers[number].ctx);
}
}
static void *_stream_worker_thread(void *v_ctx) {
struct worker_context_t *ctx = (struct worker_context_t *)v_ctx;
LOG_DEBUG("Hello! I am a worker #%u ^_^", ctx->number);
while (!*ctx->dev_stop && !*ctx->workers_stop) {
LOG_DEBUG("Worker %u waiting for a new job ...", ctx->number);
A_PTHREAD_M_LOCK(ctx->has_job_mutex);
A_PTHREAD_C_WAIT_TRUE(*ctx->has_job, ctx->has_job_cond, ctx->has_job_mutex);
A_PTHREAD_M_UNLOCK(ctx->has_job_mutex);
if (!*ctx->workers_stop) {
long double start_time;
long double last_comp_time;
start_time = now_monotonic_ms();
LOG_DEBUG("Worker %u compressing JPEG from buffer %d ...", ctx->number, ctx->buf_index);
if (encoder_compress_buffer(ctx->encoder, ctx->dev, ctx->buf_index) < 0) {
*ctx->job_failed = true;
}
if (_stream_release_buffer(ctx->dev, &ctx->buf_info) == 0) {
*ctx->job_start_time = start_time;
*ctx->has_job = false;
last_comp_time = now_monotonic_ms() - start_time;
A_PTHREAD_M_LOCK(ctx->last_comp_time_mutex);
*ctx->last_comp_time = last_comp_time;
A_PTHREAD_M_UNLOCK(ctx->last_comp_time_mutex);
LOG_VERBOSE(
"Compressed JPEG size=%ld; time=%0.3Lf; worker=%u; buffer=%d",
ctx->dev->run->pictures[ctx->buf_index].size, last_comp_time, ctx->number, ctx->buf_index
);
} else {
*ctx->job_failed = true;
*ctx->has_job = false;
}
}
A_PTHREAD_M_LOCK(ctx->free_workers_mutex);
*ctx->free_workers += 1;
A_PTHREAD_M_UNLOCK(ctx->free_workers_mutex);
A_PTHREAD_C_SIGNAL(ctx->free_workers_cond);
}
LOG_DEBUG("Bye-bye (worker %d)", ctx->number);
return NULL;
}
static void _stream_destroy_workers(struct device_t *dev, struct workers_pool_t *pool) {
if (pool->workers) {
LOG_INFO("Destroying workers ...");
*pool->workers_stop = true;
for (unsigned number = 0; number < dev->run->n_workers; ++number) {
A_PTHREAD_M_LOCK(&pool->workers[number].has_job_mutex);
pool->workers[number].has_job = true; // Final job: die
A_PTHREAD_M_UNLOCK(&pool->workers[number].has_job_mutex);
A_PTHREAD_C_SIGNAL(&pool->workers[number].has_job_cond);
A_PTHREAD_JOIN(pool->workers[number].tid);
A_PTHREAD_M_DESTROY(&pool->workers[number].has_job_mutex);
A_PTHREAD_C_DESTROY(&pool->workers[number].has_job_cond);
}
A_PTHREAD_M_DESTROY(&pool->free_workers_mutex);
A_PTHREAD_C_DESTROY(&pool->free_workers_cond);
free(pool->workers);
}
pool->free_workers = 0;
pool->workers = NULL;
}
static int _stream_control(struct device_t *dev, const bool enable) {
if (enable != dev->run->capturing) {
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
LOG_DEBUG("Calling ioctl(%s) ...", (enable ? "VIDIOC_STREAMON" : "VIDIOC_STREAMOFF"));
if (xioctl(dev->run->fd, (enable ? VIDIOC_STREAMON : VIDIOC_STREAMOFF), &type) < 0) {
LOG_PERROR("Unable to %s capturing", (enable ? "start" : "stop"));
if (enable) {
return -1;
}
}
dev->run->capturing = enable;
LOG_INFO("Capturing %s", (enable ? "started" : "stopped"));
}
return 0;
}
static int _stream_grab_buffer(struct device_t *dev, struct v4l2_buffer *buf_info) {
MEMSET_ZERO_PTR(buf_info);
buf_info->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf_info->memory = V4L2_MEMORY_MMAP;
LOG_DEBUG("Calling ioctl(VIDIOC_DQBUF) ...");
if (xioctl(dev->run->fd, VIDIOC_DQBUF, buf_info) < 0) {
LOG_PERROR("Unable to dequeue buffer");
return -1;
}
LOG_DEBUG("Got a new frame in buffer index=%d; bytesused=%d", buf_info->index, buf_info->bytesused);
if (buf_info->index >= dev->run->n_buffers) {
LOG_ERROR("Got invalid buffer index=%d; nbuffers=%d", buf_info->index, dev->run->n_buffers);
return -1;
}
return 0;
}
static int _stream_release_buffer(struct device_t *dev, struct v4l2_buffer *buf_info) {
LOG_DEBUG("Calling ioctl(VIDIOC_QBUF) ...");
if (xioctl(dev->run->fd, VIDIOC_QBUF, buf_info) < 0) {
LOG_PERROR("Unable to requeue buffer");
return -1;
}
return 0;
}
static int _stream_handle_event(struct device_t *dev) {
struct v4l2_event event;
LOG_DEBUG("Calling ioctl(VIDIOC_DQEVENT) ...");
if (!xioctl(dev->run->fd, VIDIOC_DQEVENT, &event)) {
switch (event.type) {
case V4L2_EVENT_SOURCE_CHANGE:
LOG_INFO("Got V4L2_EVENT_SOURCE_CHANGE: source changed");
return -1;
case V4L2_EVENT_EOS:
LOG_INFO("Got V4L2_EVENT_EOS: end of stream (ignored)");
return 0;
}
} else {
LOG_PERROR("Got some V4L2 device event, but where is it? ");
}
return 0;
}

View File

@@ -1,101 +0,0 @@
/*****************************************************************************
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <stdbool.h>
#include <signal.h>
#include <pthread.h>
#include "device.h"
#include "encoder.h"
struct worker_context_t {
unsigned number;
struct device_t *dev;
int buf_index;
struct v4l2_buffer buf_info;
sig_atomic_t *volatile dev_stop;
bool *workers_stop;
struct encoder_t *encoder;
pthread_mutex_t *last_comp_time_mutex;
long double *last_comp_time;
pthread_mutex_t *has_job_mutex;
bool *has_job;
bool *job_failed;
long double *job_start_time;
pthread_cond_t *has_job_cond;
pthread_mutex_t *free_workers_mutex;
unsigned *free_workers;
pthread_cond_t *free_workers_cond;
};
struct worker_t {
struct worker_context_t ctx;
pthread_t tid;
pthread_mutex_t last_comp_time_mutex;
long double last_comp_time;
pthread_mutex_t has_job_mutex;
bool has_job;
bool job_failed;
long double job_start_time;
pthread_cond_t has_job_cond;
struct worker_t *order_prev;
struct worker_t *order_next;
};
struct workers_pool_t {
struct worker_t*workers;
bool *workers_stop;
pthread_mutex_t free_workers_mutex;
unsigned free_workers;
pthread_cond_t free_workers_cond;
struct encoder_t *encoder;
};
struct stream_t {
struct picture_t picture;
unsigned width;
unsigned height;
unsigned fps;
bool updated;
pthread_mutex_t mutex;
struct device_t *dev;
struct encoder_t *encoder;
};
struct stream_t *stream_init(struct device_t *dev, struct encoder_t *encoder);
void stream_destroy(struct stream_t *stream);
void stream_loop(struct stream_t *stream);
void stream_loop_break(struct stream_t *stream);

View File

@@ -1,91 +0,0 @@
/*****************************************************************************
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <math.h>
#include <pthread.h>
#include <time.h>
#include <assert.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>
#define A_PTHREAD_CREATE(_tid, _func, _arg) assert(!pthread_create(_tid, NULL, _func, _arg))
#define A_PTHREAD_JOIN(_tid) assert(!pthread_join(_tid, NULL))
#define A_PTHREAD_M_INIT(_mutex) assert(!pthread_mutex_init(_mutex, NULL))
#define A_PTHREAD_M_DESTROY(_mutex) assert(!pthread_mutex_destroy(_mutex))
#define A_PTHREAD_M_LOCK(_mutex) assert(!pthread_mutex_lock(_mutex))
#define A_PTHREAD_M_UNLOCK(_mutex) assert(!pthread_mutex_unlock(_mutex))
#define A_PTHREAD_C_INIT(_cond) assert(!pthread_cond_init(_cond, NULL))
#define A_PTHREAD_C_DESTROY(_cond) assert(!pthread_cond_destroy(_cond))
#define A_PTHREAD_C_SIGNAL(...) assert(!pthread_cond_signal(__VA_ARGS__))
#define A_PTHREAD_C_WAIT_TRUE(_var, _cond, _mutex) { while(!_var) assert(!pthread_cond_wait(_cond, _mutex)); }
#define A_CALLOC(_dest, _nmemb) assert((_dest = calloc(_nmemb, sizeof(*(_dest)))))
#define A_REALLOC(_dest, _nmemb) assert((_dest = realloc(_dest, _nmemb * sizeof(*(_dest)))))
#define MEMSET_ZERO(_x_obj) memset(&(_x_obj), 0, sizeof(_x_obj))
#define MEMSET_ZERO_PTR(_x_ptr) memset(_x_ptr, 0, sizeof(*(_x_ptr)))
#define INLINE inline __attribute__((always_inline))
#define UNUSED __attribute__((unused))
INLINE unsigned max_u(unsigned a, unsigned b) {
return (a > b ? a : b);
}
INLINE void now_ms(clockid_t clk_id, time_t *sec, long *msec) {
struct timespec spec;
assert(!clock_gettime(clk_id, &spec));
*sec = spec.tv_sec;
*msec = round(spec.tv_nsec / 1.0e6);
if (*msec > 999) {
*sec += 1;
*msec = 0;
}
}
INLINE long double now_monotonic_ms(void) {
time_t sec;
long msec;
now_ms(CLOCK_MONOTONIC_RAW, &sec, &msec);
return (long double)sec + ((long double)msec) / 1000;
}
INLINE long double now_real_ms(void) {
time_t sec;
long msec;
now_ms(CLOCK_REALTIME, &sec, &msec);
return (long double)sec + ((long double)msec) / 1000;
}

107
src/ustreamer/blank.c Normal file
View File

@@ -0,0 +1,107 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "blank.h"
static frame_s *_init_internal(void);
static frame_s *_init_external(const char *path);
frame_s *blank_frame_init(const char *path) {
frame_s *blank = NULL;
if (path && path[0] != '\0') {
blank = _init_external(path);
}
if (blank) {
LOG_INFO("Using external blank placeholder: %s", path);
} else {
blank = _init_internal();
LOG_INFO("Using internal blank placeholder");
}
return blank;
}
static frame_s *_init_internal(void) {
frame_s *blank = frame_init();
frame_set_data(blank, BLANK_JPEG_DATA, BLANK_JPEG_DATA_SIZE);
blank->width = BLANK_JPEG_WIDTH;
blank->height = BLANK_JPEG_HEIGHT;
blank->format = V4L2_PIX_FMT_JPEG;
return blank;
}
static frame_s *_init_external(const char *path) {
FILE *fp = NULL;
frame_s *blank = frame_init();
blank->format = V4L2_PIX_FMT_JPEG;
if ((fp = fopen(path, "rb")) == NULL) {
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) {
frame_realloc_data(blank, blank->used + CHUNK_SIZE * 2);
}
size_t readed = fread(blank->data + blank->used, 1, CHUNK_SIZE, fp);
blank->used += readed;
if (readed < CHUNK_SIZE) {
if (feof(fp)) {
break;
} else {
LOG_PERROR("Can't read blank placeholder");
goto error;
}
}
}
# undef CHUNK_SIZE
frame_s *decoded = frame_init();
if (unjpeg(blank, decoded, false) < 0) {
frame_destroy(decoded);
goto error;
}
blank->width = decoded->width;
blank->height = decoded->height;
frame_destroy(decoded);
goto ok;
error:
frame_destroy(blank);
blank = NULL;
ok:
if (fp) {
fclose(fp);
}
return blank;
}

38
src/ustreamer/blank.h Normal file
View File

@@ -0,0 +1,38 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <stdio.h>
#include <stdbool.h>
#include <linux/videodev2.h>
#include "../libs/tools.h"
#include "../libs/logging.h"
#include "../libs/frame.h"
#include "../libs/unjpeg.h"
#include "data/blank_jpeg.h"
frame_s *blank_frame_init(const char *path);

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,723 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "blank_jpeg.h"
const unsigned BLANK_JPEG_WIDTH = 640;
const unsigned BLANK_JPEG_HEIGHT = 480;
const size_t BLANK_JPEG_DATA_SIZE = 13845;
const uint8_t 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,
};

View File

@@ -0,0 +1,34 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <stdint.h>
#include <sys/types.h>
extern const unsigned BLANK_JPEG_WIDTH;
extern const unsigned BLANK_JPEG_HEIGHT;
extern const size_t BLANK_JPEG_DATA_SIZE;
extern const uint8_t BLANK_JPEG_DATA[];

View File

@@ -0,0 +1,75 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>uStreamer</title>
<style>body {font-family: monospace;}</style>
</head>
<body>
<h3>&micro;Streamer v%VERSION%</h3>
<hr>
<ul>
<li>
<a href="state"><b>/state</b></a><br>
Get JSON structure with the state of the server.
</li>
<br>
<li>
<a href="snapshot"><b>/snapshot</b></a><br>
Get a current actual image from the server.
</li>
<br>
<li>
<a href="stream"><b>/stream</b></a><br>
Get a live stream. Query params:<br>
<br>
<ul>
<li>
<b>key=abc123</b><br>
The user-defined key, which is part of cookie <i>stream_client</i>, which allows<br>
the stream client to determine its identifier and view statistics using <a href="state">/state</a>.
</li>
<br>
<li>
<b>extra_headers=1</b><br>
Add <i>X-UStreamer-*</i> headers to the <a href="stream">/stream</a> handle
(like with the <a href="snapshot">/snapshot</a>).
</li>
<br>
<li>
<b>advance_headers=1</b><br>
Enable workaround for the Chromium/Blink bug
<a href="https://bugs.chromium.org/p/chromium/issues/detail?id=527446">#527446</a>.
</li>
<br>
<li>
<b>dual_final_frames=1</b><br>
Enable workaround for the Safari/WebKit bug when using option <i>--drop-same-frames</i>.<br>
Without this option, when the frame series is completed, WebKit-based browsers<br>
renders the last frame with a delay.
</li>
<br>
<li>
<b>zero_data=1</b><br>
Disables the actual sending of JPEG data and leaves only response headers.
</li>
</ul>
</li>
<br>
<li>
The mjpg-streamer compatibility layer:<br>
<br>
<ul>
<li><a href="?action=snapshot">/?action=snapshot</a> as alias to the <a href="snapshot">/snapshot</a>.</li>
<br>
<li><a href="?action=stream">/?action=stream</a> as alias to the <a href="stream">/stream</a>.</li>
</ul>
</li>
</ul>
<br>
<hr>
<a href="https://github.com/pikvm/ustreamer">Sources &amp; docs</a>
</body>
</html>

View File

@@ -0,0 +1,101 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "index_html.h"
const char *const HTML_INDEX_PAGE = " \
<!DOCTYPE html> \
\
<html> \
<head> \
<meta charset=\"utf-8\" /> \
<title>uStreamer</title> \
<style>body {font-family: monospace;}</style> \
</head> \
\
<body> \
<h3>&micro;Streamer v" VERSION "</h3> \
<hr> \
<ul> \
<li> \
<a href=\"state\"><b>/state</b></a><br> \
Get JSON structure with the state of the server. \
</li> \
<br> \
<li> \
<a href=\"snapshot\"><b>/snapshot</b></a><br> \
Get a current actual image from the server. \
</li> \
<br> \
<li> \
<a href=\"stream\"><b>/stream</b></a><br> \
Get a live stream. Query params:<br> \
<br> \
<ul> \
<li> \
<b>key=abc123</b><br> \
The user-defined key, which is part of cookie <i>stream_client</i>, which allows<br> \
the stream client to determine its identifier and view statistics using <a href=\"state\">/state</a>. \
</li> \
<br> \
<li> \
<b>extra_headers=1</b><br> \
Add <i>X-UStreamer-*</i> headers to the <a href=\"stream\">/stream</a> handle \
(like with the <a href=\"snapshot\">/snapshot</a>). \
</li> \
<br> \
<li> \
<b>advance_headers=1</b><br> \
Enable workaround for the Chromium/Blink bug \
<a href=\"https://bugs.chromium.org/p/chromium/issues/detail?id=527446\">#527446</a>. \
</li> \
<br> \
<li> \
<b>dual_final_frames=1</b><br> \
Enable workaround for the Safari/WebKit bug when using option <i>--drop-same-frames</i>.<br> \
Without this option, when the frame series is completed, WebKit-based browsers<br> \
renders the last frame with a delay. \
</li> \
<br> \
<li> \
<b>zero_data=1</b><br> \
Disables the actual sending of JPEG data and leaves only response headers. \
</li> \
</ul> \
</li> \
<br> \
<li> \
The mjpg-streamer compatibility layer:<br> \
<br> \
<ul> \
<li><a href=\"?action=snapshot\">/?action=snapshot</a> as alias to the <a href=\"snapshot\">/snapshot</a>.</li> \
<br> \
<li><a href=\"?action=stream\">/?action=stream</a> as alias to the <a href=\"stream\">/stream</a>.</li> \
</ul> \
</li> \
</ul> \
<br> \
<hr> \
<a href=\"https://github.com/pikvm/ustreamer\">Sources &amp; docs</a> \
</body> \
</html> \
";

View File

@@ -1,7 +1,8 @@
/*****************************************************************************
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -21,7 +22,9 @@
#pragma once
#include "../device.h"
#include <sys/types.h>
#include "../../libs/config.h"
void jpeg_encoder_compress_buffer(struct device_t *dev, const unsigned index, const unsigned quality);
extern const char *const HTML_INDEX_PAGE;

905
src/ustreamer/device.c Normal file
View File

@@ -0,0 +1,905 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "device.h"
static const struct {
const char *name;
const v4l2_std_id standard;
} _STANDARDS[] = {
{"UNKNOWN", V4L2_STD_UNKNOWN},
{"PAL", V4L2_STD_PAL},
{"NTSC", V4L2_STD_NTSC},
{"SECAM", V4L2_STD_SECAM},
};
static const struct {
const char *name;
const unsigned format;
} _FORMATS[] = {
{"YUYV", V4L2_PIX_FMT_YUYV},
{"UYVY", V4L2_PIX_FMT_UYVY},
{"RGB565", V4L2_PIX_FMT_RGB565},
{"RGB24", V4L2_PIX_FMT_RGB24},
{"MJPEG", V4L2_PIX_FMT_MJPEG},
{"JPEG", V4L2_PIX_FMT_JPEG},
};
static const struct {
const char *name;
const enum v4l2_memory io_method;
} _IO_METHODS[] = {
{"MMAP", V4L2_MEMORY_MMAP},
{"USERPTR", V4L2_MEMORY_USERPTR},
};
static int _device_open_check_cap(device_s *dev);
static int _device_open_dv_timings(device_s *dev);
static int _device_apply_dv_timings(device_s *dev);
static int _device_open_format(device_s *dev, bool first);
static void _device_open_hw_fps(device_s *dev);
static void _device_open_jpeg_quality(device_s *dev);
static int _device_open_io_method(device_s *dev);
static int _device_open_io_method_mmap(device_s *dev);
static int _device_open_io_method_userptr(device_s *dev);
static int _device_open_queue_buffers(device_s *dev);
static int _device_apply_resolution(device_s *dev, unsigned width, unsigned height);
static void _device_apply_controls(device_s *dev);
static int _device_query_control(
device_s *dev, struct v4l2_queryctrl *query,
const char *name, unsigned cid, bool quiet);
static void _device_set_control(
device_s *dev, struct v4l2_queryctrl *query,
const char *name, unsigned cid, int value, bool quiet);
static const char *_format_to_string_nullable(unsigned format);
static const char *_format_to_string_supported(unsigned format);
static const char *_standard_to_string(v4l2_std_id standard);
static const char *_io_method_to_string_supported(enum v4l2_memory io_method);
#define RUN(_next) dev->run->_next
device_s *device_init(void) {
device_runtime_s *run;
A_CALLOC(run, 1);
run->fd = -1;
device_s *dev;
A_CALLOC(dev, 1);
dev->path = "/dev/video0";
dev->width = 640;
dev->height = 480;
dev->format = V4L2_PIX_FMT_YUYV;
dev->jpeg_quality = 80;
dev->standard = V4L2_STD_UNKNOWN;
dev->io_method = V4L2_MEMORY_MMAP;
dev->n_bufs = get_cores_available() + 1;
dev->min_frame_size = 128;
dev->timeout = 1;
dev->run = run;
return dev;
}
void device_destroy(device_s *dev) {
free(dev->run);
free(dev);
}
int device_parse_format(const char *str) {
for (unsigned index = 0; index < ARRAY_LEN(_FORMATS); ++index) {
if (!strcasecmp(str, _FORMATS[index].name)) {
return _FORMATS[index].format;
}
}
return FORMAT_UNKNOWN;
}
v4l2_std_id device_parse_standard(const char *str) {
for (unsigned index = 1; index < ARRAY_LEN(_STANDARDS); ++index) {
if (!strcasecmp(str, _STANDARDS[index].name)) {
return _STANDARDS[index].standard;
}
}
return STANDARD_UNKNOWN;
}
int device_parse_io_method(const char *str) {
for (unsigned index = 0; index < ARRAY_LEN(_IO_METHODS); ++index) {
if (!strcasecmp(str, _IO_METHODS[index].name)) {
return _IO_METHODS[index].io_method;
}
}
return IO_METHOD_UNKNOWN;
}
int device_open(device_s *dev) {
if ((RUN(fd) = open(dev->path, O_RDWR|O_NONBLOCK)) < 0) {
LOG_PERROR("Can't open device");
goto error;
}
LOG_INFO("Device fd=%d opened", RUN(fd));
if (_device_open_check_cap(dev) < 0) {
goto error;
}
if (_device_open_dv_timings(dev) < 0) {
goto error;
}
if (_device_open_format(dev, true) < 0) {
goto error;
}
_device_open_hw_fps(dev);
_device_open_jpeg_quality(dev);
if (_device_open_io_method(dev) < 0) {
goto error;
}
if (_device_open_queue_buffers(dev) < 0) {
goto error;
}
_device_apply_controls(dev);
LOG_DEBUG("Device fd=%d initialized", RUN(fd));
return 0;
error:
device_close(dev);
return -1;
}
void device_close(device_s *dev) {
RUN(persistent_timeout_reported) = false;
if (RUN(hw_bufs)) {
LOG_DEBUG("Releasing device buffers ...");
for (unsigned index = 0; index < RUN(n_bufs); ++index) {
# define HW(_next) RUN(hw_bufs)[index]._next
if (HW(dma_fd) >= 0) {
close(HW(dma_fd));
HW(dma_fd) = -1;
}
if (dev->io_method == V4L2_MEMORY_MMAP) {
if (HW(raw.allocated) > 0 && HW(raw.data) != MAP_FAILED) {
if (munmap(HW(raw.data), HW(raw.allocated)) < 0) {
LOG_PERROR("Can't unmap device buffer=%u", index);
}
}
} else { // V4L2_MEMORY_USERPTR
if (HW(raw.data)) {
free(HW(raw.data));
}
}
# undef HW
}
RUN(n_bufs) = 0;
free(RUN(hw_bufs));
RUN(hw_bufs) = NULL;
}
if (RUN(fd) >= 0) {
LOG_DEBUG("Closing device ...");
if (close(RUN(fd)) < 0) {
LOG_PERROR("Can't close device fd=%d", RUN(fd));
} else {
LOG_INFO("Device fd=%d closed", RUN(fd));
}
RUN(fd) = -1;
}
}
int device_export_to_dma(device_s *dev) {
# define DMA_FD RUN(hw_bufs[index].dma_fd)
for (unsigned index = 0; index < RUN(n_bufs); ++index) {
struct v4l2_exportbuffer exp = {0};
exp.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
exp.index = index;
LOG_DEBUG("Exporting device buffer=%u to DMA ...", index);
if (xioctl(RUN(fd), VIDIOC_EXPBUF, &exp) < 0) {
LOG_PERROR("Can't export device buffer=%u to DMA", index);
goto error;
}
DMA_FD = exp.fd;
}
return 0;
error:
for (unsigned index = 0; index < RUN(n_bufs); ++index) {
if (DMA_FD >= 0) {
close(DMA_FD);
DMA_FD = -1;
}
}
return -1;
# undef DMA_FD
}
int device_switch_capturing(device_s *dev, bool enable) {
if (enable != RUN(capturing)) {
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
LOG_DEBUG("%s device capturing ...", (enable ? "Starting" : "Stopping"));
if (xioctl(RUN(fd), (enable ? VIDIOC_STREAMON : VIDIOC_STREAMOFF), &type) < 0) {
LOG_PERROR("Can't %s capturing", (enable ? "start" : "stop"));
if (enable) {
return -1;
}
}
RUN(capturing) = enable;
LOG_INFO("Capturing %s", (enable ? "started" : "stopped"));
}
return 0;
}
int device_select(device_s *dev, bool *has_read, bool *has_write, bool *has_error) {
int retval;
# define INIT_FD_SET(_set) \
fd_set _set; FD_ZERO(&_set); FD_SET(RUN(fd), &_set);
INIT_FD_SET(read_fds);
INIT_FD_SET(write_fds);
INIT_FD_SET(error_fds);
# undef INIT_FD_SET
struct timeval timeout;
timeout.tv_sec = dev->timeout;
timeout.tv_usec = 0;
LOG_DEBUG("Calling select() on video device ...");
retval = select(RUN(fd) + 1, &read_fds, &write_fds, &error_fds, &timeout);
if (retval > 0) {
*has_read = FD_ISSET(RUN(fd), &read_fds);
*has_write = FD_ISSET(RUN(fd), &write_fds);
*has_error = FD_ISSET(RUN(fd), &error_fds);
} else {
*has_read = false;
*has_write = false;
*has_error = false;
}
LOG_DEBUG("Device select() --> %d", retval);
if (retval > 0) {
RUN(persistent_timeout_reported) = false;
} else if (retval == 0) {
if (dev->persistent) {
if (!RUN(persistent_timeout_reported)) {
LOG_ERROR("Persistent device timeout (unplugged)");
RUN(persistent_timeout_reported) = true;
}
} else {
// Если устройство не персистентное, то таймаут является ошибкой
retval = -1;
}
}
return retval;
}
int device_grab_buffer(device_s *dev, hw_buffer_s **hw) {
*hw = NULL;
struct v4l2_buffer buf = {0};
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = dev->io_method;
LOG_DEBUG("Grabbing device buffer ...");
if (xioctl(RUN(fd), VIDIOC_DQBUF, &buf) < 0) {
LOG_PERROR("Can't grab device buffer");
return -1;
}
LOG_DEBUG("Grabbed new frame: buffer=%u, bytesused=%u", buf.index, buf.bytesused);
if (buf.index >= RUN(n_bufs)) {
LOG_ERROR("V4L2 error: grabbed invalid device buffer=%u, n_bufs=%u", buf.index, RUN(n_bufs));
return -1;
}
// Workaround for broken, corrupted frames:
// Under low light conditions corrupted frames may get captured.
// The good thing is such frames are quite small compared to the regular frames.
// For example a VGA (640x480) webcam frame is normally >= 8kByte large,
// corrupted frames are smaller.
if (buf.bytesused < dev->min_frame_size) {
LOG_DEBUG("Dropped too small frame, assuming it was broken: buffer=%u, bytesused=%u",
buf.index, buf.bytesused);
LOG_DEBUG("Releasing device buffer=%u (broken frame) ...", buf.index);
if (xioctl(RUN(fd), VIDIOC_QBUF, &buf) < 0) {
LOG_PERROR("Can't release device buffer=%u (broken frame)", buf.index);
return -1;
}
return -2;
}
# define HW(_next) RUN(hw_bufs)[buf.index]._next
if (HW(grabbed)) {
LOG_ERROR("V4L2 error: grabbed device buffer=%u is already used", buf.index);
return -1;
}
HW(grabbed) = true;
HW(raw.dma_fd) = HW(dma_fd);
HW(raw.used) = buf.bytesused;
HW(raw.width) = RUN(width);
HW(raw.height) = RUN(height);
HW(raw.format) = RUN(format);
HW(raw.stride) = RUN(stride);
HW(raw.online) = true;
memcpy(&HW(buf), &buf, sizeof(struct v4l2_buffer));
HW(raw.grab_ts) = get_now_monotonic();
# undef HW
*hw = &RUN(hw_bufs[buf.index]);
return buf.index;
}
int device_release_buffer(device_s *dev, hw_buffer_s *hw) {
const unsigned index = hw->buf.index;
LOG_DEBUG("Releasing device buffer=%u ...", index);
if (xioctl(RUN(fd), VIDIOC_QBUF, &hw->buf) < 0) {
LOG_PERROR("Can't release device buffer=%u", index);
return -1;
}
hw->grabbed = false;
return 0;
}
int device_consume_event(device_s *dev) {
struct v4l2_event event;
LOG_DEBUG("Consuming V4L2 event ...");
if (xioctl(RUN(fd), VIDIOC_DQEVENT, &event) == 0) {
switch (event.type) {
case V4L2_EVENT_SOURCE_CHANGE:
LOG_INFO("Got V4L2_EVENT_SOURCE_CHANGE: source changed");
return -1;
case V4L2_EVENT_EOS:
LOG_INFO("Got V4L2_EVENT_EOS: end of stream (ignored)");
return 0;
}
} else {
LOG_PERROR("Got some V4L2 device event, but where is it? ");
}
return 0;
}
static int _device_open_check_cap(device_s *dev) {
struct v4l2_capability cap = {0};
LOG_DEBUG("Querying device capabilities ...");
if (xioctl(RUN(fd), VIDIOC_QUERYCAP, &cap) < 0) {
LOG_PERROR("Can't query device capabilities");
return -1;
}
if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
LOG_ERROR("Video capture is not supported by device");
return -1;
}
if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
LOG_ERROR("Device doesn't support streaming IO");
return -1;
}
int input = dev->input; // Needs a pointer to int for ioctl()
LOG_INFO("Using input channel: %d", input);
if (xioctl(RUN(fd), VIDIOC_S_INPUT, &input) < 0) {
LOG_ERROR("Can't set input channel");
return -1;
}
if (dev->standard != V4L2_STD_UNKNOWN) {
LOG_INFO("Using TV standard: %s", _standard_to_string(dev->standard));
if (xioctl(RUN(fd), VIDIOC_S_STD, &dev->standard) < 0) {
LOG_ERROR("Can't set video standard");
return -1;
}
} else {
LOG_DEBUG("Using TV standard: DEFAULT");
}
return 0;
}
static int _device_open_dv_timings(device_s *dev) {
_device_apply_resolution(dev, dev->width, dev->height);
if (dev->dv_timings) {
LOG_DEBUG("Using DV-timings");
if (_device_apply_dv_timings(dev) < 0) {
return -1;
}
struct v4l2_event_subscription sub = {0};
sub.type = V4L2_EVENT_SOURCE_CHANGE;
LOG_DEBUG("Subscribing to DV-timings events ...")
if (xioctl(RUN(fd), VIDIOC_SUBSCRIBE_EVENT, &sub) < 0) {
LOG_PERROR("Can't subscribe to DV-timings events");
return -1;
}
}
return 0;
}
static int _device_apply_dv_timings(device_s *dev) {
struct v4l2_dv_timings dv = {0};
LOG_DEBUG("Calling ioctl(VIDIOC_QUERY_DV_TIMINGS) ...");
if (xioctl(RUN(fd), VIDIOC_QUERY_DV_TIMINGS, &dv) == 0) {
if (dv.type == V4L2_DV_BT_656_1120) {
// See v4l2_print_dv_timings() in the kernel
unsigned htot = V4L2_DV_BT_FRAME_WIDTH(&dv.bt);
unsigned vtot = V4L2_DV_BT_FRAME_HEIGHT(&dv.bt);
if (dv.bt.interlaced) {
vtot /= 2;
}
unsigned fps = ((htot * vtot) > 0 ? ((100 * (uint64_t)dv.bt.pixelclock)) / (htot * vtot) : 0);
LOG_INFO("Got new DV-timings: %ux%u%s%u.%02u, pixclk=%llu, vsync=%u, hsync=%u",
dv.bt.width, dv.bt.height, (dv.bt.interlaced ? "i" : "p"), fps / 100, fps % 100,
(unsigned long long)dv.bt.pixelclock, dv.bt.vsync, dv.bt.hsync); // See #11 about %llu
} else {
LOG_INFO("Got new DV-timings: %ux%u, pixclk=%llu, vsync=%u, hsync=%u",
dv.bt.width, dv.bt.height,
(unsigned long long)dv.bt.pixelclock, dv.bt.vsync, dv.bt.hsync);
}
LOG_DEBUG("Calling ioctl(VIDIOC_S_DV_TIMINGS) ...");
if (xioctl(RUN(fd), VIDIOC_S_DV_TIMINGS, &dv) < 0) {
LOG_PERROR("Failed to set DV-timings");
return -1;
}
if (_device_apply_resolution(dev, dv.bt.width, dv.bt.height) < 0) {
return -1;
}
} else {
LOG_DEBUG("Calling ioctl(VIDIOC_QUERYSTD) ...");
if (xioctl(RUN(fd), VIDIOC_QUERYSTD, &dev->standard) == 0) {
LOG_INFO("Applying the new VIDIOC_S_STD: %s ...", _standard_to_string(dev->standard));
if (xioctl(RUN(fd), VIDIOC_S_STD, &dev->standard) < 0) {
LOG_PERROR("Can't set video standard");
return -1;
}
}
}
return 0;
}
static int _device_open_format(device_s *dev, bool first) {
const unsigned stride = align_size(RUN(width), 32) << 1;
struct v4l2_format fmt = {0};
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = RUN(width);
fmt.fmt.pix.height = RUN(height);
fmt.fmt.pix.pixelformat = dev->format;
fmt.fmt.pix.field = V4L2_FIELD_ANY;
fmt.fmt.pix.bytesperline = stride;
// Set format
LOG_DEBUG("Probing device format=%s, stride=%u, resolution=%ux%u ...",
_format_to_string_supported(dev->format), stride, RUN(width), RUN(height));
if (xioctl(RUN(fd), VIDIOC_S_FMT, &fmt) < 0) {
LOG_PERROR("Can't set device format");
return -1;
}
// Check resolution
bool retry = false;
if (fmt.fmt.pix.width != RUN(width) || fmt.fmt.pix.height != RUN(height)) {
LOG_ERROR("Requested resolution=%ux%u is unavailable", RUN(width), RUN(height));
retry = true;
}
if (_device_apply_resolution(dev, fmt.fmt.pix.width, fmt.fmt.pix.height) < 0) {
return -1;
}
if (first && retry) {
return _device_open_format(dev, false);
}
LOG_INFO("Using resolution: %ux%u", RUN(width), RUN(height));
// Check format
if (fmt.fmt.pix.pixelformat != dev->format) {
LOG_ERROR("Could not obtain the requested format=%s; driver gave us %s",
_format_to_string_supported(dev->format),
_format_to_string_supported(fmt.fmt.pix.pixelformat));
char *format_str;
if ((format_str = (char *)_format_to_string_nullable(fmt.fmt.pix.pixelformat)) != NULL) {
LOG_INFO("Falling back to format=%s", format_str);
} else {
char fourcc_str[8];
LOG_ERROR("Unsupported format=%s (fourcc)",
fourcc_to_string(fmt.fmt.pix.pixelformat, fourcc_str, 8));
return -1;
}
}
RUN(format) = fmt.fmt.pix.pixelformat;
LOG_INFO("Using format: %s", _format_to_string_supported(RUN(format)));
RUN(stride) = fmt.fmt.pix.bytesperline;
RUN(raw_size) = fmt.fmt.pix.sizeimage; // Only for userptr
return 0;
}
static void _device_open_hw_fps(device_s *dev) {
RUN(hw_fps) = 0;
struct v4l2_streamparm setfps = {0};
setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
LOG_DEBUG("Querying HW FPS ...");
if (xioctl(RUN(fd), VIDIOC_G_PARM, &setfps) < 0) {
if (errno == ENOTTY) { // Quiet message for TC358743
LOG_INFO("Querying HW FPS changing is not supported");
} else {
LOG_PERROR("Can't query HW FPS changing");
}
return;
}
if (!(setfps.parm.capture.capability & V4L2_CAP_TIMEPERFRAME)) {
LOG_INFO("Changing HW FPS is not supported");
return;
}
# define SETFPS_TPF(_next) setfps.parm.capture.timeperframe._next
MEMSET_ZERO(setfps);
setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
SETFPS_TPF(numerator) = 1;
SETFPS_TPF(denominator) = (dev->desired_fps == 0 ? 255 : dev->desired_fps);
if (xioctl(RUN(fd), VIDIOC_S_PARM, &setfps) < 0) {
LOG_PERROR("Can't set HW FPS");
return;
}
if (SETFPS_TPF(numerator) != 1) {
LOG_ERROR("Invalid HW FPS numerator: %u != 1", SETFPS_TPF(numerator));
return;
}
if (SETFPS_TPF(denominator) == 0) { // Не знаю, бывает ли так, но пускай на всякий случай
LOG_ERROR("Invalid HW FPS denominator: 0");
return;
}
RUN(hw_fps) = SETFPS_TPF(denominator);
if (dev->desired_fps != RUN(hw_fps)) {
LOG_INFO("Using HW FPS: %u -> %u (coerced)", dev->desired_fps, RUN(hw_fps));
} else {
LOG_INFO("Using HW FPS: %u", RUN(hw_fps));
}
# undef SETFPS_TPF
}
static void _device_open_jpeg_quality(device_s *dev) {
unsigned quality = 0;
if (is_jpeg(RUN(format))) {
struct v4l2_jpegcompression comp = {0};
if (xioctl(RUN(fd), VIDIOC_G_JPEGCOMP, &comp) < 0) {
LOG_ERROR("Device doesn't support setting of HW encoding quality parameters");
} else {
comp.quality = dev->jpeg_quality;
if (xioctl(RUN(fd), VIDIOC_S_JPEGCOMP, &comp) < 0) {
LOG_ERROR("Can't change MJPEG quality for JPEG source with HW pass-through encoder");
} else {
quality = dev->jpeg_quality;
}
}
}
RUN(jpeg_quality) = quality;
}
static int _device_open_io_method(device_s *dev) {
LOG_INFO("Using IO method: %s", _io_method_to_string_supported(dev->io_method));
switch (dev->io_method) {
case V4L2_MEMORY_MMAP: return _device_open_io_method_mmap(dev);
case V4L2_MEMORY_USERPTR: return _device_open_io_method_userptr(dev);
default: assert(0 && "Unsupported IO method");
}
return -1;
}
static int _device_open_io_method_mmap(device_s *dev) {
struct v4l2_requestbuffers req = {0};
req.count = dev->n_bufs;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
LOG_DEBUG("Requesting %u device buffers for MMAP ...", req.count);
if (xioctl(RUN(fd), VIDIOC_REQBUFS, &req) < 0) {
LOG_PERROR("Device '%s' doesn't support MMAP method", dev->path);
return -1;
}
if (req.count < 1) {
LOG_ERROR("Insufficient buffer memory: %u", req.count);
return -1;
} else {
LOG_INFO("Requested %u device buffers, got %u", dev->n_bufs, req.count);
}
LOG_DEBUG("Allocating device buffers ...");
A_CALLOC(RUN(hw_bufs), req.count);
for (RUN(n_bufs) = 0; RUN(n_bufs) < req.count; ++RUN(n_bufs)) {
struct v4l2_buffer buf = {0};
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = RUN(n_bufs);
LOG_DEBUG("Calling ioctl(VIDIOC_QUERYBUF) for device buffer=%u ...", RUN(n_bufs));
if (xioctl(RUN(fd), VIDIOC_QUERYBUF, &buf) < 0) {
LOG_PERROR("Can't VIDIOC_QUERYBUF");
return -1;
}
# define HW(_next) RUN(hw_bufs)[RUN(n_bufs)]._next
HW(dma_fd) = -1;
LOG_DEBUG("Mapping device buffer=%u ...", RUN(n_bufs));
if ((HW(raw.data) = mmap(
NULL,
buf.length,
PROT_READ | PROT_WRITE,
MAP_SHARED,
RUN(fd),
buf.m.offset
)) == MAP_FAILED) {
LOG_PERROR("Can't map device buffer=%u", RUN(n_bufs));
return -1;
}
HW(raw.allocated) = buf.length;
# undef HW
}
return 0;
}
static int _device_open_io_method_userptr(device_s *dev) {
struct v4l2_requestbuffers req = {0};
req.count = dev->n_bufs;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_USERPTR;
LOG_DEBUG("Requesting %u device buffers for USERPTR ...", req.count);
if (xioctl(RUN(fd), VIDIOC_REQBUFS, &req) < 0) {
LOG_PERROR("Device '%s' doesn't support USERPTR method", dev->path);
return -1;
}
if (req.count < 1) {
LOG_ERROR("Insufficient buffer memory: %u", req.count);
return -1;
} else {
LOG_INFO("Requested %u device buffers, got %u", dev->n_bufs, req.count);
}
LOG_DEBUG("Allocating device buffers ...");
A_CALLOC(RUN(hw_bufs), req.count);
const unsigned page_size = getpagesize();
const unsigned buf_size = align_size(RUN(raw_size), page_size);
for (RUN(n_bufs) = 0; RUN(n_bufs) < req.count; ++RUN(n_bufs)) {
# define HW(_next) RUN(hw_bufs)[RUN(n_bufs)]._next
assert(HW(raw.data) = aligned_alloc(page_size, buf_size));
memset(HW(raw.data), 0, buf_size);
HW(raw.allocated) = buf_size;
# undef HW
}
return 0;
}
static int _device_open_queue_buffers(device_s *dev) {
for (unsigned index = 0; index < RUN(n_bufs); ++index) {
struct v4l2_buffer buf = {0};
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = dev->io_method;
buf.index = index;
if (dev->io_method == V4L2_MEMORY_USERPTR) {
buf.m.userptr = (unsigned long)RUN(hw_bufs)[index].raw.data;
buf.length = RUN(hw_bufs)[index].raw.allocated;
}
LOG_DEBUG("Calling ioctl(VIDIOC_QBUF) for buffer=%u ...", index);
if (xioctl(RUN(fd), VIDIOC_QBUF, &buf) < 0) {
LOG_PERROR("Can't VIDIOC_QBUF");
return -1;
}
}
return 0;
}
static int _device_apply_resolution(device_s *dev, unsigned width, unsigned height) {
// Тут VIDEO_MIN_* не используются из-за странностей минимального разрешения при отсутствии сигнала
// у некоторых устройств, например TC358743
if (
width == 0 || width > VIDEO_MAX_WIDTH
|| height == 0 || height > VIDEO_MAX_HEIGHT
) {
LOG_ERROR("Requested forbidden resolution=%ux%u: min=1x1, max=%ux%u",
width, height, VIDEO_MAX_WIDTH, VIDEO_MAX_HEIGHT);
return -1;
}
RUN(width) = width;
RUN(height) = height;
return 0;
}
static void _device_apply_controls(device_s *dev) {
# define SET_CID_VALUE(_cid, _field, _value, _quiet) { \
struct v4l2_queryctrl query; \
if (_device_query_control(dev, &query, #_field, _cid, _quiet) == 0) { \
_device_set_control(dev, &query, #_field, _cid, _value, _quiet); \
} \
}
# define SET_CID_DEFAULT(_cid, _field, _quiet) { \
struct v4l2_queryctrl query; \
if (_device_query_control(dev, &query, #_field, _cid, _quiet) == 0) { \
_device_set_control(dev, &query, #_field, _cid, query.default_value, _quiet); \
} \
}
# define CONTROL_MANUAL_CID(_cid, _field) { \
if (dev->ctl._field.mode == CTL_MODE_VALUE) { \
SET_CID_VALUE(_cid, _field, dev->ctl._field.value, false); \
} else if (dev->ctl._field.mode == CTL_MODE_DEFAULT) { \
SET_CID_DEFAULT(_cid, _field, false); \
} \
}
# define CONTROL_AUTO_CID(_cid_auto, _cid_manual, _field) { \
if (dev->ctl._field.mode == CTL_MODE_VALUE) { \
SET_CID_VALUE(_cid_auto, _field##_auto, 0, true); \
SET_CID_VALUE(_cid_manual, _field, dev->ctl._field.value, false); \
} else if (dev->ctl._field.mode == CTL_MODE_AUTO) { \
SET_CID_VALUE(_cid_auto, _field##_auto, 1, false); \
} else if (dev->ctl._field.mode == CTL_MODE_DEFAULT) { \
SET_CID_VALUE(_cid_auto, _field##_auto, 0, true); /* Reset inactive flag */ \
SET_CID_DEFAULT(_cid_manual, _field, false); \
SET_CID_DEFAULT(_cid_auto, _field##_auto, false); \
} \
}
CONTROL_AUTO_CID (V4L2_CID_AUTOBRIGHTNESS, V4L2_CID_BRIGHTNESS, brightness);
CONTROL_MANUAL_CID ( V4L2_CID_CONTRAST, contrast);
CONTROL_MANUAL_CID ( V4L2_CID_SATURATION, saturation);
CONTROL_AUTO_CID (V4L2_CID_HUE_AUTO, V4L2_CID_HUE, hue);
CONTROL_MANUAL_CID ( V4L2_CID_GAMMA, gamma);
CONTROL_MANUAL_CID ( V4L2_CID_SHARPNESS, sharpness);
CONTROL_MANUAL_CID ( V4L2_CID_BACKLIGHT_COMPENSATION, backlight_compensation);
CONTROL_AUTO_CID (V4L2_CID_AUTO_WHITE_BALANCE, V4L2_CID_WHITE_BALANCE_TEMPERATURE, white_balance);
CONTROL_AUTO_CID (V4L2_CID_AUTOGAIN, V4L2_CID_GAIN, gain);
CONTROL_MANUAL_CID ( V4L2_CID_COLORFX, color_effect);
CONTROL_MANUAL_CID ( V4L2_CID_ROTATE, rotate);
CONTROL_MANUAL_CID ( V4L2_CID_VFLIP, flip_vertical);
CONTROL_MANUAL_CID ( V4L2_CID_HFLIP, flip_horizontal);
# undef CONTROL_AUTO_CID
# undef CONTROL_MANUAL_CID
# undef SET_CID_DEFAULT
# undef SET_CID_VALUE
}
static int _device_query_control(
device_s *dev, struct v4l2_queryctrl *query,
const char *name, unsigned cid, bool quiet) {
// cppcheck-suppress redundantPointerOp
MEMSET_ZERO(*query);
query->id = cid;
if (xioctl(RUN(fd), VIDIOC_QUERYCTRL, query) < 0 || query->flags & V4L2_CTRL_FLAG_DISABLED) {
if (!quiet) {
LOG_ERROR("Changing control %s is unsupported", name);
}
return -1;
}
return 0;
}
static void _device_set_control(
device_s *dev, struct v4l2_queryctrl *query,
const char *name, unsigned cid, int value, bool quiet) {
if (value < query->minimum || value > query->maximum || value % query->step != 0) {
if (!quiet) {
LOG_ERROR("Invalid value %d of control %s: min=%d, max=%d, default=%d, step=%u",
value, name, query->minimum, query->maximum, query->default_value, query->step);
}
return;
}
struct v4l2_control ctl = {0};
ctl.id = cid;
ctl.value = value;
if (xioctl(RUN(fd), VIDIOC_S_CTRL, &ctl) < 0) {
if (!quiet) {
LOG_PERROR("Can't set control %s", name);
}
} else if (!quiet) {
LOG_INFO("Applying control %s: %d", name, ctl.value);
}
}
static const char *_format_to_string_nullable(unsigned format) {
for (unsigned index = 0; index < ARRAY_LEN(_FORMATS); ++index) {
if (format == _FORMATS[index].format) {
return _FORMATS[index].name;
}
}
return NULL;
}
static const char *_format_to_string_supported(unsigned format) {
const char *format_str = _format_to_string_nullable(format);
return (format_str == NULL ? "unsupported" : format_str);
}
static const char *_standard_to_string(v4l2_std_id standard) {
for (unsigned index = 0; index < ARRAY_LEN(_STANDARDS); ++index) {
if (standard == _STANDARDS[index].standard) {
return _STANDARDS[index].name;
}
}
return _STANDARDS[0].name;
}
static const char *_io_method_to_string_supported(enum v4l2_memory io_method) {
for (unsigned index = 0; index < ARRAY_LEN(_IO_METHODS); ++index) {
if (io_method == _IO_METHODS[index].io_method) {
return _IO_METHODS[index].name;
}
}
return "unsupported";
}
# undef RUN

157
src/ustreamer/device.h Normal file
View File

@@ -0,0 +1,157 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <stdlib.h>
#include <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 <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/logging.h"
#include "../libs/threading.h"
#include "../libs/frame.h"
#include "xioctl.h"
#define VIDEO_MIN_WIDTH ((unsigned)160)
#define VIDEO_MAX_WIDTH ((unsigned)10240)
#define VIDEO_MIN_HEIGHT ((unsigned)120)
#define VIDEO_MAX_HEIGHT ((unsigned)4320)
#define VIDEO_MAX_FPS ((unsigned)120)
#define STANDARD_UNKNOWN V4L2_STD_UNKNOWN
#define STANDARDS_STR "PAL, NTSC, SECAM"
#define FORMAT_UNKNOWN -1
#define FORMATS_STR "YUYV, UYVY, RGB565, RGB24, MJPEG, JPEG"
#define IO_METHOD_UNKNOWN -1
#define IO_METHODS_STR "MMAP, USERPTR"
typedef struct {
frame_s raw;
struct v4l2_buffer buf;
int dma_fd;
bool grabbed;
} hw_buffer_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;
hw_buffer_s *hw_bufs;
bool capturing;
bool persistent_timeout_reported;
} device_runtime_s;
typedef enum {
CTL_MODE_NONE = 0,
CTL_MODE_VALUE,
CTL_MODE_AUTO,
CTL_MODE_DEFAULT,
} control_mode_e;
typedef struct {
control_mode_e mode;
int value;
} control_s;
typedef struct {
control_s brightness;
control_s contrast;
control_s saturation;
control_s hue;
control_s gamma;
control_s sharpness;
control_s backlight_compensation;
control_s white_balance;
control_s gain;
control_s color_effect;
control_s rotate;
control_s flip_vertical;
control_s flip_horizontal;
} controls_s;
typedef struct {
char *path;
unsigned input;
unsigned width;
unsigned height;
unsigned format;
unsigned 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;
bool persistent;
unsigned timeout;
controls_s ctl;
device_runtime_s *run;
} device_s;
device_s *device_init(void);
void device_destroy(device_s *dev);
int device_parse_format(const char *str);
v4l2_std_id device_parse_standard(const char *str);
int device_parse_io_method(const char *str);
int device_open(device_s *dev);
void device_close(device_s *dev);
int device_export_to_dma(device_s *dev);
int device_switch_capturing(device_s *dev, bool enable);
int device_select(device_s *dev, bool *has_read, bool *has_write, bool *has_error);
int device_grab_buffer(device_s *dev, hw_buffer_s **hw);
int device_release_buffer(device_s *dev, hw_buffer_s *hw);
int device_consume_event(device_s *dev);

248
src/ustreamer/encoder.c Normal file
View File

@@ -0,0 +1,248 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "encoder.h"
static const struct {
const char *name;
const encoder_type_e type;
} _ENCODER_TYPES[] = {
{"CPU", ENCODER_TYPE_CPU},
{"HW", ENCODER_TYPE_HW},
{"M2M-VIDEO", ENCODER_TYPE_M2M_VIDEO},
{"M2M-IMAGE", ENCODER_TYPE_M2M_IMAGE},
{"M2M-MJPEG", ENCODER_TYPE_M2M_VIDEO},
{"M2M-JPEG", ENCODER_TYPE_M2M_IMAGE},
{"OMX", ENCODER_TYPE_M2M_IMAGE},
{"NOOP", ENCODER_TYPE_NOOP},
};
static void *_worker_job_init(void *v_enc);
static void _worker_job_destroy(void *v_job);
static bool _worker_run_job(worker_s *wr);
#define ER(_next) enc->run->_next
encoder_s *encoder_init(void) {
encoder_runtime_s *run;
A_CALLOC(run, 1);
run->type = ENCODER_TYPE_CPU;
run->quality = 80;
A_MUTEX_INIT(&run->mutex);
encoder_s *enc;
A_CALLOC(enc, 1);
enc->type = run->type;
enc->n_workers = get_cores_available();
enc->run = run;
return enc;
}
void encoder_destroy(encoder_s *enc) {
if (ER(m2ms)) {
for (unsigned index = 0; index < ER(n_m2ms); ++index) {
if (ER(m2ms[index])) {
m2m_encoder_destroy(ER(m2ms[index]));
}
}
free(ER(m2ms));
}
A_MUTEX_DESTROY(&ER(mutex));
free(enc->run);
free(enc);
}
encoder_type_e encoder_parse_type(const char *str) {
for (unsigned index = 0; index < ARRAY_LEN(_ENCODER_TYPES); ++index) {
if (!strcasecmp(str, _ENCODER_TYPES[index].name)) {
return _ENCODER_TYPES[index].type;
}
}
return ENCODER_TYPE_UNKNOWN;
}
const char *encoder_type_to_string(encoder_type_e type) {
for (unsigned index = 0; index < ARRAY_LEN(_ENCODER_TYPES); ++index) {
if (_ENCODER_TYPES[index].type == type) {
return _ENCODER_TYPES[index].name;
}
}
return _ENCODER_TYPES[0].name;
}
workers_pool_s *encoder_workers_pool_init(encoder_s *enc, device_s *dev) {
# define DR(_next) dev->run->_next
encoder_type_e type = (ER(cpu_forced) ? ENCODER_TYPE_CPU : enc->type);
unsigned quality = dev->jpeg_quality;
unsigned n_workers = min_u(enc->n_workers, DR(n_bufs));
bool cpu_forced = false;
if (is_jpeg(DR(format)) && type != ENCODER_TYPE_HW) {
LOG_INFO("Switching to HW encoder: the input is (M)JPEG ...");
type = ENCODER_TYPE_HW;
}
if (type == ENCODER_TYPE_HW) {
if (!is_jpeg(DR(format))) {
LOG_INFO("Switching to CPU encoder: the input format is not (M)JPEG ...");
goto use_cpu;
}
quality = DR(jpeg_quality);
n_workers = 1;
} else if (type == ENCODER_TYPE_M2M_VIDEO || type == ENCODER_TYPE_M2M_IMAGE) {
LOG_DEBUG("Preparing M2M-%s encoder ...", (type == ENCODER_TYPE_M2M_VIDEO ? "VIDEO" : "IMAGE"));
if (ER(m2ms) == NULL) {
A_CALLOC(ER(m2ms), n_workers);
}
for (; ER(n_m2ms) < n_workers; ++ER(n_m2ms)) {
// Начинаем с нуля и доинициализируем на следующих заходах при необходимости
char name[32];
snprintf(name, 32, "JPEG-%u", ER(n_m2ms));
if (type == ENCODER_TYPE_M2M_VIDEO) {
ER(m2ms[ER(n_m2ms)]) = m2m_mjpeg_encoder_init(name, enc->m2m_path, quality);
} else {
ER(m2ms[ER(n_m2ms)]) = m2m_jpeg_encoder_init(name, enc->m2m_path, quality);
}
}
} else if (type == ENCODER_TYPE_NOOP) {
n_workers = 1;
quality = 0;
}
goto ok;
use_cpu:
type = ENCODER_TYPE_CPU;
quality = dev->jpeg_quality;
ok:
if (type == ENCODER_TYPE_NOOP) {
LOG_INFO("Using JPEG NOOP encoder");
} else if (quality == 0) {
LOG_INFO("Using JPEG quality: encoder default");
} else {
LOG_INFO("Using JPEG quality: %u%%", quality);
}
A_MUTEX_LOCK(&ER(mutex));
ER(type) = type;
ER(quality) = quality;
if (cpu_forced) {
ER(cpu_forced) = true;
}
A_MUTEX_UNLOCK(&ER(mutex));
long double desired_interval = 0;
if (dev->desired_fps > 0 && (dev->desired_fps < dev->run->hw_fps || dev->run->hw_fps == 0)) {
desired_interval = (long double)1 / dev->desired_fps;
}
return workers_pool_init(
"JPEG", "jw", n_workers, desired_interval,
_worker_job_init, (void *)enc,
_worker_job_destroy,
_worker_run_job);
# undef DR
}
void encoder_get_runtime_params(encoder_s *enc, encoder_type_e *type, unsigned *quality) {
A_MUTEX_LOCK(&ER(mutex));
*type = ER(type);
*quality = ER(quality);
A_MUTEX_UNLOCK(&ER(mutex));
}
static void *_worker_job_init(void *v_enc) {
encoder_job_s *job;
A_CALLOC(job, 1);
job->enc = (encoder_s *)v_enc;
job->dest = frame_init();
return (void *)job;
}
static void _worker_job_destroy(void *v_job) {
encoder_job_s *job = (encoder_job_s *)v_job;
frame_destroy(job->dest);
free(job);
}
#undef ER
static bool _worker_run_job(worker_s *wr) {
encoder_job_s *job = (encoder_job_s *)wr->job;
frame_s *src = &job->hw->raw;
frame_s *dest = job->dest;
# define ER(_next) job->enc->run->_next
assert(ER(type) != ENCODER_TYPE_UNKNOWN);
if (ER(type) == ENCODER_TYPE_CPU) {
LOG_VERBOSE("Compressing JPEG using CPU: worker=%s, buffer=%u",
wr->name, job->hw->buf.index);
cpu_encoder_compress(src, dest, ER(quality));
} else if (ER(type) == ENCODER_TYPE_HW) {
LOG_VERBOSE("Compressing JPEG using HW (just copying): worker=%s, buffer=%u",
wr->name, job->hw->buf.index);
hw_encoder_compress(src, dest);
} else if (ER(type) == ENCODER_TYPE_M2M_VIDEO || ER(type) == ENCODER_TYPE_M2M_IMAGE) {
LOG_VERBOSE("Compressing JPEG using M2M-%s: worker=%s, buffer=%u",
(ER(type) == ENCODER_TYPE_M2M_VIDEO ? "VIDEO" : "IMAGE"), wr->name, job->hw->buf.index);
if (m2m_encoder_compress(ER(m2ms[wr->number]), src, dest, false) < 0) {
goto error;
}
} else if (ER(type) == ENCODER_TYPE_NOOP) {
LOG_VERBOSE("Compressing JPEG using NOOP (do nothing): worker=%s, buffer=%u",
wr->name, job->hw->buf.index);
usleep(5000); // Просто чтобы работала логика desired_fps
dest->encode_end_ts = get_now_monotonic();
}
LOG_VERBOSE("Compressed new JPEG: size=%zu, time=%0.3Lf, worker=%s, buffer=%u",
job->dest->used,
job->dest->encode_end_ts - job->dest->encode_begin_ts,
wr->name,
job->hw->buf.index);
return true;
error:
LOG_ERROR("Compression failed: worker=%s, buffer=%u", wr->name, job->hw->buf.index);
LOG_ERROR("Error while compressing buffer, falling back to CPU");
A_MUTEX_LOCK(&ER(mutex));
ER(cpu_forced) = true;
A_MUTEX_UNLOCK(&ER(mutex));
return false;
# undef ER
}

91
src/ustreamer/encoder.h Normal file
View File

@@ -0,0 +1,91 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <stdlib.h>
#include <stdbool.h>
#include <strings.h>
#include <assert.h>
#include <pthread.h>
#include <linux/videodev2.h>
#include "../libs/tools.h"
#include "../libs/threading.h"
#include "../libs/logging.h"
#include "../libs/frame.h"
#include "device.h"
#include "workers.h"
#include "m2m.h"
#include "encoders/cpu/encoder.h"
#include "encoders/hw/encoder.h"
#define ENCODER_TYPES_STR "CPU, HW, M2M-VIDEO, M2M-IMAGE, NOOP"
typedef enum {
ENCODER_TYPE_UNKNOWN, // Only for encoder_parse_type() and main()
ENCODER_TYPE_CPU,
ENCODER_TYPE_HW,
ENCODER_TYPE_M2M_VIDEO,
ENCODER_TYPE_M2M_IMAGE,
ENCODER_TYPE_NOOP,
} encoder_type_e;
typedef struct {
encoder_type_e type;
unsigned quality;
bool cpu_forced;
pthread_mutex_t mutex;
unsigned n_m2ms;
m2m_encoder_s **m2ms;
} encoder_runtime_s;
typedef struct {
encoder_type_e type;
unsigned n_workers;
char *m2m_path;
encoder_runtime_s *run;
} encoder_s;
typedef struct {
encoder_s *enc;
hw_buffer_s *hw;
frame_s *dest;
} encoder_job_s;
encoder_s *encoder_init(void);
void encoder_destroy(encoder_s *enc);
encoder_type_e encoder_parse_type(const char *str);
const char *encoder_type_to_string(encoder_type_e type);
workers_pool_s *encoder_workers_pool_init(encoder_s *enc, device_s *dev);
void encoder_get_runtime_params(encoder_s *enc, encoder_type_e *type, unsigned *quality);
int encoder_compress(encoder_s *enc, unsigned worker_number, frame_s *src, frame_s *dest);

View File

@@ -0,0 +1,273 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# This source file based on code of MJPG-Streamer. #
# #
# Copyright (C) 2005-2006 Laurent Pinchart & Michel Xhaard #
# Copyright (C) 2006 Gabriel A. Devenyi #
# Copyright (C) 2007 Tom Stöveken #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "encoder.h"
typedef struct {
struct jpeg_destination_mgr mgr; // Default manager
JOCTET *buf; // Start of buffer
frame_s *frame;
} _jpeg_dest_manager_s;
static void _jpeg_set_dest_frame(j_compress_ptr jpeg, frame_s *frame);
static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg, const frame_s *frame);
static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg, const frame_s *frame);
static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, const frame_s *frame);
static void _jpeg_write_scanlines_rgb24(struct jpeg_compress_struct *jpeg, const frame_s *frame);
static void _jpeg_init_destination(j_compress_ptr jpeg);
static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg);
static void _jpeg_term_destination(j_compress_ptr jpeg);
void cpu_encoder_compress(const frame_s *src, frame_s *dest, unsigned quality) {
// This function based on compress_image_to_jpeg() from mjpg-streamer
frame_encoding_begin(src, dest, V4L2_PIX_FMT_JPEG);
struct jpeg_compress_struct jpeg;
struct jpeg_error_mgr jpeg_error;
jpeg.err = jpeg_std_error(&jpeg_error);
jpeg_create_compress(&jpeg);
_jpeg_set_dest_frame(&jpeg, dest);
jpeg.image_width = src->width;
jpeg.image_height = src->height;
jpeg.input_components = 3;
jpeg.in_color_space = JCS_RGB;
jpeg_set_defaults(&jpeg);
jpeg_set_quality(&jpeg, quality, TRUE);
jpeg_start_compress(&jpeg, TRUE);
# define WRITE_SCANLINES(_format, _func) \
case _format: { _func(&jpeg, src); break; }
switch (src->format) {
// https://www.fourcc.org/yuv.php
WRITE_SCANLINES(V4L2_PIX_FMT_YUYV, _jpeg_write_scanlines_yuyv);
WRITE_SCANLINES(V4L2_PIX_FMT_UYVY, _jpeg_write_scanlines_uyvy);
WRITE_SCANLINES(V4L2_PIX_FMT_RGB565, _jpeg_write_scanlines_rgb565);
WRITE_SCANLINES(V4L2_PIX_FMT_RGB24, _jpeg_write_scanlines_rgb24);
default: assert(0 && "Unsupported input format for CPU encoder");
}
# undef WRITE_SCANLINES
jpeg_finish_compress(&jpeg);
jpeg_destroy_compress(&jpeg);
frame_encoding_end(dest);
}
static void _jpeg_set_dest_frame(j_compress_ptr jpeg, frame_s *frame) {
if (jpeg->dest == NULL) {
assert((jpeg->dest = (struct jpeg_destination_mgr *)(*jpeg->mem->alloc_small)(
(j_common_ptr) jpeg, JPOOL_PERMANENT, sizeof(_jpeg_dest_manager_s)
)));
}
_jpeg_dest_manager_s *dest = (_jpeg_dest_manager_s *)jpeg->dest;
dest->mgr.init_destination = _jpeg_init_destination;
dest->mgr.empty_output_buffer = _jpeg_empty_output_buffer;
dest->mgr.term_destination = _jpeg_term_destination;
dest->frame = frame;
frame->used = 0;
}
#define YUV_R(_y, _, _v) (((_y) + (359 * (_v))) >> 8)
#define YUV_G(_y, _u, _v) (((_y) - (88 * (_u)) - (183 * (_v))) >> 8)
#define YUV_B(_y, _u, _) (((_y) + (454 * (_u))) >> 8)
#define NORM_COMPONENT(_x) (((_x) > 255) ? 255 : (((_x) < 0) ? 0 : (_x)))
static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg, const frame_s *frame) {
uint8_t *line_buf;
A_CALLOC(line_buf, frame->width * 3);
const unsigned padding = frame_get_padding(frame);
const uint8_t *data = frame->data;
unsigned z = 0;
while (jpeg->next_scanline < frame->height) {
uint8_t *ptr = line_buf;
for (unsigned x = 0; x < frame->width; ++x) {
int y = (!z ? data[0] << 8 : data[2] << 8);
int u = data[1] - 128;
int v = data[3] - 128;
int r = YUV_R(y, u, v);
int g = YUV_G(y, u, v);
int b = YUV_B(y, u, v);
*(ptr++) = NORM_COMPONENT(r);
*(ptr++) = NORM_COMPONENT(g);
*(ptr++) = NORM_COMPONENT(b);
if (z++) {
z = 0;
data += 4;
}
}
data += padding;
JSAMPROW scanlines[1] = {line_buf};
jpeg_write_scanlines(jpeg, scanlines, 1);
}
free(line_buf);
}
static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg, const frame_s *frame) {
uint8_t *line_buf;
A_CALLOC(line_buf, frame->width * 3);
const unsigned padding = frame_get_padding(frame);
const uint8_t *data = frame->data;
unsigned z = 0;
while (jpeg->next_scanline < frame->height) {
uint8_t *ptr = line_buf;
for (unsigned x = 0; x < frame->width; ++x) {
int y = (!z ? data[1] << 8 : data[3] << 8);
int u = data[0] - 128;
int v = data[2] - 128;
int r = YUV_R(y, u, v);
int g = YUV_G(y, u, v);
int b = YUV_B(y, u, v);
*(ptr++) = NORM_COMPONENT(r);
*(ptr++) = NORM_COMPONENT(g);
*(ptr++) = NORM_COMPONENT(b);
if (z++) {
z = 0;
data += 4;
}
}
data += padding;
JSAMPROW scanlines[1] = {line_buf};
jpeg_write_scanlines(jpeg, scanlines, 1);
}
free(line_buf);
}
#undef NORM_COMPONENT
#undef YUV_B
#undef YUV_G
#undef YUV_R
static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, const frame_s *frame) {
uint8_t *line_buf;
A_CALLOC(line_buf, frame->width * 3);
const unsigned padding = frame_get_padding(frame);
const uint8_t *data = frame->data;
while (jpeg->next_scanline < frame->height) {
uint8_t *ptr = line_buf;
for (unsigned x = 0; x < frame->width; ++x) {
unsigned int two_byte = (data[1] << 8) + data[0];
*(ptr++) = data[1] & 248; // Red
*(ptr++) = (uint8_t)((two_byte & 2016) >> 3); // Green
*(ptr++) = (data[0] & 31) * 8; // Blue
data += 2;
}
data += padding;
JSAMPROW scanlines[1] = {line_buf};
jpeg_write_scanlines(jpeg, scanlines, 1);
}
free(line_buf);
}
static void _jpeg_write_scanlines_rgb24(struct jpeg_compress_struct *jpeg, const frame_s *frame) {
const unsigned padding = frame_get_padding(frame);
uint8_t *data = frame->data;
while (jpeg->next_scanline < frame->height) {
JSAMPROW scanlines[1] = {data};
jpeg_write_scanlines(jpeg, scanlines, 1);
data += (jpeg->next_scanline * frame->width * 3) + padding;
}
}
#define JPEG_OUTPUT_BUFFER_SIZE ((size_t)4096)
static void _jpeg_init_destination(j_compress_ptr jpeg) {
_jpeg_dest_manager_s *dest = (_jpeg_dest_manager_s *)jpeg->dest;
// Allocate the output buffer - it will be released when done with image
assert((dest->buf = (JOCTET *)(*jpeg->mem->alloc_small)(
(j_common_ptr) jpeg, JPOOL_IMAGE, JPEG_OUTPUT_BUFFER_SIZE * sizeof(JOCTET)
)));
dest->mgr.next_output_byte = dest->buf;
dest->mgr.free_in_buffer = JPEG_OUTPUT_BUFFER_SIZE;
}
static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg) {
// Called whenever local jpeg buffer fills up
_jpeg_dest_manager_s *dest = (_jpeg_dest_manager_s *)jpeg->dest;
frame_append_data(dest->frame, dest->buf, JPEG_OUTPUT_BUFFER_SIZE);
dest->mgr.next_output_byte = dest->buf;
dest->mgr.free_in_buffer = JPEG_OUTPUT_BUFFER_SIZE;
return TRUE;
}
static void _jpeg_term_destination(j_compress_ptr jpeg) {
// Called by jpeg_finish_compress after all data has been written.
// Usually needs to flush buffer.
_jpeg_dest_manager_s *dest = (_jpeg_dest_manager_s *)jpeg->dest;
size_t final = JPEG_OUTPUT_BUFFER_SIZE - dest->mgr.free_in_buffer;
// Write any data remaining in the buffer.
frame_append_data(dest->frame, dest->buf, final);
}
#undef JPEG_OUTPUT_BUFFER_SIZE

View File

@@ -0,0 +1,38 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>
#include <jpeglib.h>
#include <linux/videodev2.h>
#include "../../../libs/tools.h"
#include "../../../libs/frame.h"
void cpu_encoder_compress(const frame_s *src, frame_s *dest, unsigned quality);

View File

@@ -0,0 +1,81 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# This source file based on code of MJPG-Streamer. #
# #
# Copyright (C) 2005-2006 Laurent Pinchart & Michel Xhaard #
# Copyright (C) 2006 Gabriel A. Devenyi #
# Copyright (C) 2007 Tom Stöveken #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "encoder.h"
void _copy_plus_huffman(const frame_s *src, frame_s *dest);
static bool _is_huffman(const uint8_t *data);
void hw_encoder_compress(const frame_s *src, frame_s *dest) {
assert(is_jpeg(src->format));
_copy_plus_huffman(src, dest);
}
void _copy_plus_huffman(const frame_s *src, frame_s *dest) {
frame_encoding_begin(src, dest, V4L2_PIX_FMT_JPEG);
if (!_is_huffman(src->data)) {
const uint8_t *src_ptr = src->data;
const uint8_t *src_end = src->data + src->used;
while ((((src_ptr[0] << 8) | src_ptr[1]) != 0xFFC0) && (src_ptr < src_end)) {
src_ptr += 1;
}
if (src_ptr >= src_end) {
dest->used = 0; // Error
return;
}
const size_t paste = src_ptr - src->data;
frame_set_data(dest, src->data, paste);
frame_append_data(dest, HUFFMAN_TABLE, sizeof(HUFFMAN_TABLE));
frame_append_data(dest, src_ptr, src->used - paste);
} else {
frame_set_data(dest, src->data, src->used);
}
frame_encoding_end(dest);
}
static bool _is_huffman(const uint8_t *data) {
unsigned count = 0;
while ((((uint16_t)data[0] << 8) | data[1]) != 0xFFDA) {
if (count++ > 2048) {
return false;
}
if ((((uint16_t)data[0] << 8) | data[1]) == 0xFFC4) {
return true;
}
data += 1;
}
return false;
}

View File

@@ -0,0 +1,37 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include <linux/videodev2.h>
#include "../../../libs/frame.h"
#include "huffman.h"
void hw_encoder_compress(const frame_s *src, frame_s *dest);

View File

@@ -0,0 +1,69 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# This source file based on code of MJPG-Streamer. #
# #
# Copyright (C) 2005-2006 Laurent Pinchart & Michel Xhaard #
# Copyright (C) 2006 Gabriel A. Devenyi #
# Copyright (C) 2007 Tom Stöveken #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <stdint.h>
static const uint8_t HUFFMAN_TABLE[] = {
0xFF, 0xC4, 0x01, 0xA2, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02,
0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x01, 0x00, 0x03,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
0x0A, 0x0B, 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05,
0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7D, 0x01, 0x02, 0x03, 0x00, 0x04,
0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22,
0x71, 0x14, 0x32, 0x81, 0x91, 0xA1, 0x08, 0x23, 0x42, 0xB1, 0xC1, 0x15,
0x52, 0xD1, 0xF0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0A, 0x16, 0x17,
0x18, 0x19, 0x1A, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x34, 0x35, 0x36,
0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A,
0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66,
0x67, 0x68, 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A,
0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95,
0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8,
0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2,
0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5,
0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7,
0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9,
0xFA, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05,
0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 0x00, 0x01, 0x02, 0x03, 0x11, 0x04,
0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22,
0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xA1, 0xB1, 0xC1, 0x09, 0x23, 0x33,
0x52, 0xF0, 0x15, 0x62, 0x72, 0xD1, 0x0A, 0x16, 0x24, 0x34, 0xE1, 0x25,
0xF1, 0x17, 0x18, 0x19, 0x1A, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x35, 0x36,
0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A,
0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66,
0x67, 0x68, 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A,
0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94,
0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA,
0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4,
0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7,
0xE8, 0xE9, 0xEA, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA,
};

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