Compare commits

...

94 Commits
v5.40 ... v5.57

Author SHA1 Message Date
Maxim Devaev
8f0acb2176 Bump version: 5.56 → 5.57 2024-03-01 04:33:13 +02:00
Maxim Devaev
8edeff8160 fixed makefile 2024-03-01 04:21:45 +02:00
Maxim Devaev
cacec0d25c refactoring 2024-03-01 04:09:16 +02:00
Maxim Devaev
c54d7da19f Bump version: 5.55 → 5.56 2024-03-01 03:48:15 +02:00
Maxim Devaev
df610e1045 improved makefiles 2024-03-01 03:47:03 +02:00
Maxim Devaev
8d4e6a13b0 server: fixed zero frame behaviour 2024-03-01 03:20:43 +02:00
Maxim Devaev
c852e5f827 lint fixes 2024-03-01 00:43:57 +02:00
Maxim Devaev
2a37f1650b using text generator for blank images 2024-03-01 00:39:19 +02:00
Maxim Devaev
15f160c874 refactoring 2024-02-29 22:40:51 +02:00
Maxim Devaev
df63ad4678 refactoring 2024-02-29 22:40:51 +02:00
Markus Küffner
72cd61cccf docker: build images for armv6 (#262) 2024-02-29 22:13:38 +02:00
Maxim Devaev
3e5a444023 refactoring 2024-02-29 19:17:41 +02:00
Maxim Devaev
a3f4294cd8 refactoring 2024-02-29 18:21:26 +02:00
Maxim Devaev
d9401b70f5 commented unused us_queue_get_free() 2024-02-29 08:07:47 +02:00
Maxim Devaev
4296d5dada janus: using ring for audio pipeline 2024-02-29 08:06:48 +02:00
Maxim Devaev
12937b93d5 janus: using ring buffers for rtp pipelines 2024-02-29 06:57:16 +02:00
Maxim Devaev
28cd5e5b87 janus: reducing memory allocating using ring buffer 2024-02-29 06:27:11 +02:00
Maxim Devaev
7bacef7622 minor fix 2024-02-29 03:29:23 +02:00
Maxim Devaev
ca3638313b moved ftext to libs as frametext 2024-02-29 03:10:48 +02:00
Maxim Devaev
bb3e4ec2c7 moved queue from janus to common libs 2024-02-29 02:48:52 +02:00
Maxim Devaev
d52ac784f6 device: don't expose dma for jpeg 2024-02-29 02:42:44 +02:00
Maxim Devaev
f5a9214dd4 simplified 2024-02-29 01:23:41 +02:00
Maxim Devaev
5b54a8dbe2 v4p: fixed buf error handling 2024-02-29 00:52:03 +02:00
Maxim Devaev
32165e97ed simplified 2024-02-29 00:42:34 +02:00
Maxim Devaev
4b6f65881d simplified 2024-02-28 23:25:06 +02:00
Maxim Devaev
0e7d8da407 Bump version: 5.54 → 5.55 2024-02-28 00:14:42 +02:00
Maxim Devaev
1c862b21e9 v4p: better messages 2024-02-28 00:13:37 +02:00
Maxim Devaev
b202438a4d v4p: ignore ENOENT (unplugged) on restoring saved_crtc 2024-02-27 23:59:02 +02:00
Maxim Devaev
50ee2ba964 v4p: simplified 2024-02-27 23:50:27 +02:00
Maxim Devaev
9f40713ee1 v4p: check display status via sysfs 2024-02-27 07:55:33 +02:00
Maxim Devaev
5f393ae972 Bump version: 5.53 → 5.54 2024-02-27 00:14:06 +02:00
Maxim Devaev
aca3ee9e23 fix installer 2024-02-27 00:12:57 +02:00
Maxim Devaev
284b17d1db Bump version: 5.52 → 5.53 2024-02-26 22:42:48 +02:00
Maxim Devaev
506a4496d1 fix 2024-02-26 22:41:48 +02:00
Maxim Devaev
b5c201d1c1 Bump version: 5.51 → 5.52 2024-02-26 21:42:53 +02:00
Maxim Devaev
032faa9882 refactoring 2024-02-26 21:29:26 +02:00
Maxim Devaev
414baadc40 refactoring 2024-02-26 21:14:36 +02:00
Maxim Devaev
2d6716aa47 refactoring 2024-02-26 20:28:09 +02:00
Maxim Devaev
260619923a new macro US_MIN() and US_MAX() 2024-02-26 19:09:16 +02:00
Maxim Devaev
3715c89ec8 v4p 2024-02-26 17:36:43 +02:00
Maxim Devaev
5026108079 device: Logging prefix
(cherry picked from commit f3bfdb2fd74c0bb5249df3f21ab6daf0ec12318a)
Signed-off-by: Maxim Devaev <mdevaev@gmail.com>
2024-02-23 04:03:43 +00:00
Maxim Devaev
5f7c556697 device: Keep DV-timing Hz
(cherry picked from commit 17a448223c0dd1cf420ba9a79232f433ff11c4b5)
Signed-off-by: Maxim Devaev <mdevaev@gmail.com>
2024-02-23 04:03:37 +00:00
Maxim Devaev
3c7564da19 big refactoring 2024-02-22 19:35:49 +02:00
Maxim Devaev
f0e070be5b Bump version: 5.50 → 5.51 2024-02-19 00:40:14 +02:00
Maxim Devaev
aa05c470b3 string fixes 2024-02-19 00:22:45 +02:00
Maxim Devaev
13af11a3a6 Using strerror_r() instead of strerror_l() for better compatibility 2024-02-18 18:41:33 +02:00
Maxim Devaev
41330940c6 Bump version: 5.49 → 5.50 2024-02-16 17:15:26 +02:00
Maxim Devaev
46e630d2f6 using (void) instead of UNUSED 2024-02-16 01:51:36 +02:00
Maxim Devaev
63d87f0526 add instance_id to stream_client cookie name 2024-02-15 15:52:03 +02:00
Maxim Devaev
b578e9897e notes about libcamerify 2024-02-03 20:14:19 +02:00
Maxim Devaev
b2ebcf99c8 Bump version: 5.48 → 5.49 2024-02-02 22:53:48 +02:00
Maxim Devaev
6a6b910869 refactoring 2024-02-02 22:41:45 +02:00
Maxim Devaev
4e8acf371f Issue #252: YVYU support 2024-02-02 22:41:45 +02:00
Thomer Gil
c4cb8288c7 Fix it's vs its typo (#255)
Co-authored-by: Thomer Gil <thomer@thomer.com>
2024-01-30 18:28:32 +02:00
Maxim Devaev
2dddb879bc Bump version: 5.47 → 5.48 2024-01-09 00:12:23 +02:00
Maxim Devaev
4d92dc662c removed some checks in gpio 2024-01-09 00:11:11 +02:00
Maxim Devaev
3cb649d97c Bump version: 5.46 → 5.47 2024-01-07 04:48:07 +02:00
Maxim Devaev
d9b5f2b03d Issue #255: libgpiod 2.0 API supported
Thanks for @jpalus for #249 with compatibility code
2024-01-07 04:36:53 +02:00
Jan Palus
2997906d98 add option to make verbose builds (#251)
if V=1 is passed to make echo invoked command verbosely
2024-01-02 02:27:12 +02:00
Jan Palus
bcd3deaa13 remove remainings of rpi vc paths (#250)
accidently left after Makefile split and subsequently switching to v4l2
based encoding
2024-01-02 02:24:37 +02:00
Maxim Devaev
f8ed7d7b3b Bump version: 5.45 → 5.46 2023-12-14 13:29:50 +02:00
Maxim Devaev
622f5cf1eb janus plugin: increased video queue 2023-12-14 12:41:29 +02:00
Maxim Devaev
81756811f3 update for bookworm 2023-11-15 14:59:34 +02:00
Maxim Devaev
bd403593a0 using libjpeg62-turbo on raspbian 2023-11-15 14:52:11 +02:00
Jason Huggins
fc3e0232e1 '-e' should be '--encoder' (#241) 2023-11-03 17:39:18 +02:00
Maxim Devaev
89fd83bfc1 added info for v2 camera 2023-10-28 11:05:55 +03:00
Maxim Devaev
83a77ea898 doc about youtube streaming 2023-10-24 20:18:07 +03:00
Maxim Devaev
33fdf9bf43 Bump version: 5.44 → 5.45 2023-10-24 14:14:00 +03:00
Ed Maste
6bd4ef59c0 avoid duplicate increment of slc (#239)
Clang reported "warning: variable 'slc' is incremented both in the loop
header and in the loop body".
2023-10-24 05:11:36 +03:00
Maxim Devaev
79987da1bf Bump version: 5.43 → 5.44 2023-10-14 01:47:24 +03:00
Maxim Devaev
05e5db09e4 fix 2023-10-12 04:19:23 +03:00
Maxim Devaev
55e432a529 Merge branch 'mp' 2023-10-12 04:13:10 +03:00
chr
4732c85ec4 Optimize JPEG scanline copy of yuv format (#235)
* opt jpeg scanline copy with yuv format

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

* sync with upstream version

* fix use local variable after free

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

* request buffer length = VIDEO_MAX_PLANES for multi-planar devices

---------

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

Replaces #229

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

* added down the road section
2023-10-04 02:41:39 +03:00
Maxim Devaev
35ed415f4c Bump version: 5.41 → 5.42 2023-08-23 07:54:17 +03:00
Maxim Devaev
121edf5a10 lint fix 2023-08-23 07:21:18 +03:00
Maxim Devaev
aa90ed1fbb lint fix 2023-08-23 07:11:57 +03:00
Maxim Devaev
a102a4a3db refactoring 2023-08-23 07:08:02 +03:00
Maxim Devaev
516c0be6ea decreased grab latency 2023-08-23 05:26:35 +03:00
Maxim Devaev
0745f0a75a lint fixes 2023-08-23 02:41:41 +03:00
Maxim Devaev
90e51c0619 always using CLOCK_MONOTONIC 2023-08-23 01:19:30 +03:00
Maxim Devaev
cb9c1658af ignoring ustreamer.egg-info 2023-08-23 00:42:49 +03:00
Maxim Devaev
548c261d92 Bump version: 5.40 → 5.41 2023-06-19 21:31:49 +03:00
Maxim Devaev
d4560fcba9 pikvm/ustreamer#221: Increased resolution limit 2023-06-19 21:31:09 +03:00
Maxim Devaev
370434601c example for camera module 3 2023-06-08 20:41:33 +03:00
101 changed files with 4219 additions and 2990 deletions

View File

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

View File

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

1
.gitignore vendored
View File

@@ -4,6 +4,7 @@
/pkg/arch/src/
/src/build/
/python/build/
/python/ustreamer.egg-info/
/janus/build/
/ustreamer
/ustreamer-dump

View File

@@ -9,9 +9,6 @@ PY ?= python3
CFLAGS ?= -O3
LDFLAGS ?=
RPI_VC_HEADERS ?= /opt/vc/include
RPI_VC_LIBS ?= /opt/vc/lib
export
_LINTERS_IMAGE ?= ustreamer-linters
@@ -22,6 +19,9 @@ define optbool
$(filter $(shell echo $(1) | tr A-Z a-z), yes on 1)
endef
ifeq ($(V),)
ECHO = @
endif
# =====
all:
@@ -36,18 +36,19 @@ endif
apps:
$(MAKE) -C src
@ ln -sf src/ustreamer.bin ustreamer
@ ln -sf src/ustreamer-dump.bin ustreamer-dump
for i in src/*.bin; do \
test ! -x $$i || ln -sf $$i `basename $$i .bin`; \
done
python:
$(MAKE) -C python
@ ln -sf python/build/lib.*/*.so .
$(ECHO) ln -sf python/build/lib.*/*.so .
janus:
$(MAKE) -C janus
@ ln -sf janus/*.so .
$(ECHO) ln -sf janus/*.so .
install: all
@@ -70,9 +71,8 @@ install-strip: install
regen:
tools/$(MAKE)-jpeg-h.py src/ustreamer/data/blank.jpeg src/ustreamer/data/blank_jpeg.c BLANK
tools/$(MAKE)-ico-h.py src/ustreamer/data/favicon.ico src/ustreamer/data/favicon_ico.c FAVICON
tools/$(MAKE)-html-h.py src/ustreamer/data/index.html src/ustreamer/data/index_html.c INDEX
tools/make-ico-h.py src/ustreamer/data/favicon.ico src/ustreamer/data/favicon_ico.c FAVICON
tools/make-html-h.py src/ustreamer/data/index.html src/ustreamer/data/index_html.c INDEX
release:
@@ -119,7 +119,7 @@ clean-all: linters clean
clean:
rm -rf pkg/arch/pkg pkg/arch/src pkg/arch/v*.tar.gz pkg/arch/ustreamer-*.pkg.tar.{xz,zst}
rm -f ustreamer ustreamer-dump *.so
rm -f ustreamer ustreamer-* *.so
$(MAKE) -C src clean
$(MAKE) -C python clean
$(MAKE) -C janus clean

View File

@@ -47,15 +47,13 @@ You need to download the µStreamer onto your system and build it from the sourc
You'll need ```make```, ```gcc```, ```libevent``` with ```pthreads``` support, ```libjpeg9```/```libjpeg-turbo``` and ```libbsd``` (only for Linux).
* Arch: `sudo pacman -S libevent libjpeg-turbo libutil-linux libbsd`.
* Raspbian: `sudo apt install libevent-dev libjpeg9-dev libbsd-dev`. Add `libgpiod-dev` for `WITH_GPIO=1` and `libsystemd-dev` for `WITH_SYSTEMD=1` and `libasound2-dev libspeex-dev libspeexdsp-dev libopus-dev` for `WITH_JANUS=1`.
* Raspberry OS Bullseye: `sudo apt install libevent-dev libjpeg62-turbo libbsd-dev`. Add `libgpiod-dev` for `WITH_GPIO=1` and `libsystemd-dev` for `WITH_SYSTEMD=1` and `libasound2-dev libspeex-dev libspeexdsp-dev libopus-dev` for `WITH_JANUS=1`.
* Raspberry OS Bookworm: same as previous but replace `libjpeg62-turbo` to `libjpeg62-turbo-dev`.
* Debian/Ubuntu: `sudo apt install build-essential libevent-dev libjpeg-dev libbsd-dev`.
* Alpine: `sudo apk add libevent-dev libbsd-dev libjpeg-turbo-dev musl-dev`. Build with `WITH_PTHREAD_NP=0`.
To enable GPIO support install [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) and pass option ```WITH_GPIO=1```. If the compiler reports about a missing function ```pthread_get_name_np()``` (or similar), add option ```WITH_PTHREAD_NP=0``` (it's enabled by default). For the similar error with ```setproctitle()``` add option ```WITH_SETPROCTITLE=0```.
> **Note**
> Raspian: In case your version of Raspian is too old for there to be a libjpeg9 package, use `libjpeg8-dev` instead: `E: Package 'libjpeg9-dev' has no installation candidate`.
### Make
The most convenient process is to clone the µStreamer Git repository onto your system. If you don't have Git installed and don't want to install it either, you can download and unzip the sources from GitHub using `wget https://github.com/pikvm/ustreamer/archive/refs/heads/master.zip`.
@@ -88,7 +86,7 @@ Without arguments, ```ustreamer``` will try to open ```/dev/video0``` with 640x4
:exclamation: Please note that since µStreamer v2.0 cross-domain requests were disabled by default for [security reasons](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). To enable the old behavior, use the option `--allow-origin=\*`.
The recommended way of running µStreamer with [Auvidea B101](https://www.raspberrypi.org/forums/viewtopic.php?f=38&t=120702&start=400#p1339178) on Raspberry Pi:
```bash
```
$ ./ustreamer \
--format=uyvy \ # Device input format
--encoder=m2m-image \ # Hardware encoding on V4L2 M2M driver
@@ -115,7 +113,7 @@ dtoverlay=tc358743
Check size of CMA:
```bash
```
$ dmesg | grep cma-reserved
[ 0.000000] Memory: 7700524K/8244224K available (11772K kernel code, 1278K rwdata, 4320K rodata, 4096K init, 1077K bss, 281556K reserved, 262144K cma-reserved)
```
@@ -131,14 +129,14 @@ Save changes and reboot.
## Launch
Start container:
```bash
```
$ docker run --device /dev/video0:/dev/video0 -e EDID=1 -p 8080:8080 pikvm/ustreamer:latest
```
Then access the web interface at port 8080 (e.g. http://raspberrypi.local:8080).
## Custom config
```bash
```
$ docker run --rm pikvm/ustreamer:latest \
--format=uyvy \
--workers=3 \
@@ -153,8 +151,16 @@ Add `-e EDID=1` to set HDMI EDID before starting ustreamer. Use together with `-
-----
# Raspberry Pi Camera Example
Example usage for the Raspberry Pi v3 camera (required `libcamerify` which is located in `libcamera-tools` and `libcamera-v4l2` (install both) on Raspbian):
```
$ sudo modprobe bcm2835-v4l2
$ libcamerify ./ustreamer --host :: --encoder=m2m-image
```
For v2 camera you can use the same trick with `libcamerify` but enable legacy camera mode in `raspi-config`.
Example usage for the Raspberry Pi v1 camera:
```bash
```
$ sudo modprobe bcm2835-v4l2
$ ./ustreamer --host :: -m jpeg --device-timeout=5 --buffers=3 -r 2592x1944
```
@@ -163,7 +169,7 @@ $ ./ustreamer --host :: -m jpeg --device-timeout=5 --buffers=3 -r 2592x1944
: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
```
@@ -174,7 +180,7 @@ $ modprobe bcm2835-v4l2 max_video_width=2592 max_video_height=1944
µStreamer supports bandwidth-efficient streaming using [H.264 compression](https://en.wikipedia.org/wiki/Advanced_Video_Coding) and the Janus WebRTC server. See the [Janus integration guide](docs/h264.md) for full details.
## Nginx
When uStreamer is behind an Nginx proxy, it's buffering behavior introduces latency into the video stream. It's possible to disable Nginx's buffering to eliminate the additional latency:
When uStreamer is behind an Nginx proxy, its buffering behavior introduces latency into the video stream. It's possible to disable Nginx's buffering to eliminate the additional latency:
```nginx
location /stream {

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

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

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

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

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

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

97
docs/youtube.md Normal file
View File

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

View File

@@ -31,13 +31,13 @@ endif
# =====
$(_PLUGIN): $(_SRCS:%.c=$(_BUILD)/%.o)
$(info == SO $@)
@ $(CC) $^ -o $@ $(_LDFLAGS)
$(ECHO) $(CC) $^ -o $@ $(_LDFLAGS)
$(_BUILD)/%.o: %.c
$(info -- CC $<)
@ mkdir -p $(dir $@) || true
@ $(CC) $< -o $@ $(_CFLAGS)
$(ECHO) mkdir -p $(dir $@) || true
$(ECHO) $(CC) $< -o $@ $(_CFLAGS)

View File

@@ -51,6 +51,9 @@ typedef struct {
} _enc_buffer_s;
static _pcm_buffer_s *_pcm_buffer_init(void);
static _enc_buffer_s *_enc_buffer_init(void);
static void *_pcm_thread(void *v_audio);
static void *_encoder_thread(void *v_audio);
@@ -72,8 +75,8 @@ us_audio_s *us_audio_init(const char *name, unsigned pcm_hz) {
us_audio_s *audio;
US_CALLOC(audio, 1);
audio->pcm_hz = pcm_hz;
audio->pcm_queue = us_queue_init(8);
audio->enc_queue = us_queue_init(8);
US_RING_INIT_WITH_ITEMS(audio->pcm_ring, 8, _pcm_buffer_init);
US_RING_INIT_WITH_ITEMS(audio->enc_ring, 8, _enc_buffer_init);
atomic_init(&audio->stop, false);
int err;
@@ -151,8 +154,8 @@ void us_audio_destroy(us_audio_s *audio) {
US_DELETE(audio->res, speex_resampler_destroy);
US_DELETE(audio->pcm, snd_pcm_close);
US_DELETE(audio->pcm_params, snd_pcm_hw_params_free);
US_QUEUE_DELETE_WITH_ITEMS(audio->enc_queue, free);
US_QUEUE_DELETE_WITH_ITEMS(audio->pcm_queue, free);
US_RING_DELETE_WITH_ITEMS(audio->enc_ring, free);
US_RING_DELETE_WITH_ITEMS(audio->pcm_ring, free);
if (audio->tids_created) {
US_JLOG_INFO("audio", "Pipeline closed");
}
@@ -163,19 +166,32 @@ int us_audio_get_encoded(us_audio_s *audio, uint8_t *data, size_t *size, uint64_
if (atomic_load(&audio->stop)) {
return -1;
}
_enc_buffer_s *buf;
if (!us_queue_get(audio->enc_queue, (void **)&buf, 0.1)) {
if (*size < buf->used) {
free(buf);
return -3;
}
memcpy(data, buf->data, buf->used);
*size = buf->used;
*pts = buf->pts;
free(buf);
return 0;
const int ri = us_ring_consumer_acquire(audio->enc_ring, 0.1);
if (ri < 0) {
return -2;
}
return -2;
const _enc_buffer_s *const buf = audio->enc_ring->items[ri];
if (*size < buf->used) {
us_ring_consumer_release(audio->enc_ring, ri);
return -3;
}
memcpy(data, buf->data, buf->used);
*size = buf->used;
*pts = buf->pts;
us_ring_consumer_release(audio->enc_ring, ri);
return 0;
}
static _pcm_buffer_s *_pcm_buffer_init(void) {
_pcm_buffer_s *buf;
US_CALLOC(buf, 1);
return buf;
}
static _enc_buffer_s *_enc_buffer_init(void) {
_enc_buffer_s *buf;
US_CALLOC(buf, 1);
return buf;
}
static void *_pcm_thread(void *v_audio) {
@@ -194,13 +210,13 @@ static void *_pcm_thread(void *v_audio) {
break;
}
if (us_queue_get_free(audio->pcm_queue)) {
_pcm_buffer_s *out;
US_CALLOC(out, 1);
const int ri = us_ring_producer_acquire(audio->pcm_ring, 0);
if (ri >= 0) {
_pcm_buffer_s *const out = audio->pcm_ring->items[ri];
memcpy(out->data, in, audio->pcm_size);
assert(!us_queue_put(audio->pcm_queue, out, 0));
us_ring_producer_release(audio->pcm_ring, ri);
} else {
US_JLOG_ERROR("audio", "PCM queue is full");
US_JLOG_ERROR("audio", "PCM ring is full");
}
}
@@ -215,39 +231,44 @@ static void *_encoder_thread(void *v_audio) {
int16_t in_res[_MAX_BUF16];
while (!atomic_load(&audio->stop)) {
_pcm_buffer_s *in;
if (!us_queue_get(audio->pcm_queue, (void **)&in, 0.1)) {
int16_t *in_ptr;
if (audio->res != NULL) {
assert(audio->pcm_hz != _ENCODER_INPUT_HZ);
uint32_t in_count = audio->pcm_frames;
uint32_t out_count = _HZ_TO_FRAMES(_ENCODER_INPUT_HZ);
speex_resampler_process_interleaved_int(audio->res, in->data, &in_count, in_res, &out_count);
in_ptr = in_res;
} else {
assert(audio->pcm_hz == _ENCODER_INPUT_HZ);
in_ptr = in->data;
}
const int in_ri = us_ring_consumer_acquire(audio->pcm_ring, 0.1);
if (in_ri < 0) {
continue;
}
_pcm_buffer_s *const in = audio->pcm_ring->items[in_ri];
_enc_buffer_s *out;
US_CALLOC(out, 1);
const int size = opus_encode(audio->enc, in_ptr, _HZ_TO_FRAMES(_ENCODER_INPUT_HZ), out->data, US_ARRAY_LEN(out->data));
free(in);
if (size < 0) {
_JLOG_PERROR_OPUS(size, "audio", "Fatal: Can't encode PCM frame to OPUS");
free(out);
break;
}
int16_t *in_ptr;
if (audio->res != NULL) {
assert(audio->pcm_hz != _ENCODER_INPUT_HZ);
uint32_t in_count = audio->pcm_frames;
uint32_t out_count = _HZ_TO_FRAMES(_ENCODER_INPUT_HZ);
speex_resampler_process_interleaved_int(audio->res, in->data, &in_count, in_res, &out_count);
in_ptr = in_res;
} else {
assert(audio->pcm_hz == _ENCODER_INPUT_HZ);
in_ptr = in->data;
}
const int out_ri = us_ring_producer_acquire(audio->enc_ring, 0);
if (out_ri < 0) {
US_JLOG_ERROR("audio", "OPUS encoder queue is full");
us_ring_consumer_release(audio->pcm_ring, in_ri);
continue;
}
_enc_buffer_s *const out = audio->enc_ring->items[out_ri];
const int size = opus_encode(audio->enc, in_ptr, _HZ_TO_FRAMES(_ENCODER_INPUT_HZ), out->data, US_ARRAY_LEN(out->data));
us_ring_consumer_release(audio->pcm_ring, in_ri);
if (size >= 0) {
out->used = size;
out->pts = audio->pts;
// https://datatracker.ietf.org/doc/html/rfc7587#section-4.2
audio->pts += _HZ_TO_FRAMES(_ENCODER_INPUT_HZ);
if (us_queue_put(audio->enc_queue, out, 0) != 0) {
US_JLOG_ERROR("audio", "OPUS encoder queue is full");
free(out);
}
} else {
_JLOG_PERROR_OPUS(size, "audio", "Fatal: Can't encode PCM frame to OPUS");
}
us_ring_producer_release(audio->enc_ring, out_ri);
}
atomic_store(&audio->stop, true);

View File

@@ -37,10 +37,10 @@
#include "uslibs/tools.h"
#include "uslibs/array.h"
#include "uslibs/ring.h"
#include "uslibs/threading.h"
#include "logging.h"
#include "queue.h"
typedef struct {
@@ -52,8 +52,8 @@ typedef struct {
SpeexResamplerState *res;
OpusEncoder *enc;
us_queue_s *pcm_queue;
us_queue_s *enc_queue;
us_ring_s *pcm_ring;
us_ring_s *enc_ring;
uint32_t pts;
pthread_t pcm_tid;

View File

@@ -22,6 +22,22 @@
#include "client.h"
#include <stdlib.h>
#include <stdatomic.h>
#include <string.h>
#include <pthread.h>
#include <janus/plugins/plugin.h>
#include "uslibs/types.h"
#include "uslibs/tools.h"
#include "uslibs/threading.h"
#include "uslibs/list.h"
#include "uslibs/ring.h"
#include "logging.h"
#include "rtp.h"
static void *_video_thread(void *v_client);
static void *_audio_thread(void *v_client);
@@ -38,10 +54,10 @@ us_janus_client_s *us_janus_client_init(janus_callbacks *gw, janus_plugin_sessio
atomic_init(&client->stop, false);
client->video_queue = us_queue_init(1024);
US_RING_INIT_WITH_ITEMS(client->video_ring, 2048, us_rtp_init);
US_THREAD_CREATE(client->video_tid, _video_thread, client);
client->audio_queue = us_queue_init(64);
US_RING_INIT_WITH_ITEMS(client->audio_ring, 64, us_rtp_init);
US_THREAD_CREATE(client->audio_tid, _audio_thread, client);
return client;
@@ -49,14 +65,12 @@ us_janus_client_s *us_janus_client_init(janus_callbacks *gw, janus_plugin_sessio
void us_janus_client_destroy(us_janus_client_s *client) {
atomic_store(&client->stop, true);
us_queue_put(client->video_queue, NULL, 0);
us_queue_put(client->audio_queue, NULL, 0);
US_THREAD_JOIN(client->video_tid);
US_QUEUE_DELETE_WITH_ITEMS(client->video_queue, us_rtp_destroy);
US_RING_DELETE_WITH_ITEMS(client->video_ring, us_rtp_destroy);
US_THREAD_JOIN(client->audio_tid);
US_QUEUE_DELETE_WITH_ITEMS(client->audio_queue, us_rtp_destroy);
US_RING_DELETE_WITH_ITEMS(client->audio_ring, us_rtp_destroy);
free(client);
}
@@ -66,12 +80,15 @@ void us_janus_client_send(us_janus_client_s *client, const us_rtp_s *rtp) {
atomic_load(&client->transmit)
&& (rtp->video || atomic_load(&client->transmit_audio))
) {
us_rtp_s *const new = us_rtp_dup(rtp);
if (us_queue_put((new->video ? client->video_queue : client->audio_queue), new, 0) != 0) {
US_JLOG_ERROR("client", "Session %p %s queue is full",
client->session, (new->video ? "video" : "audio"));
us_rtp_destroy(new);
us_ring_s *const ring = (rtp->video ? client->video_ring : client->audio_ring);
const int ri = us_ring_producer_acquire(ring, 0);
if (ri < 0) {
US_JLOG_ERROR("client", "Session %p %s ring is full",
client->session, (rtp->video ? "video" : "audio"));
return;
}
memcpy(ring->items[ri], rtp, sizeof(us_rtp_s));
us_ring_producer_release(ring, ri);
}
}
@@ -85,43 +102,45 @@ static void *_audio_thread(void *v_client) {
static void *_common_thread(void *v_client, bool video) {
us_janus_client_s *const client = (us_janus_client_s *)v_client;
us_queue_s *const queue = (video ? client->video_queue : client->audio_queue);
assert(queue != NULL); // Audio may be NULL
us_ring_s *const ring = (video ? client->video_ring : client->audio_ring);
assert(ring != NULL); // Audio may be NULL
while (!atomic_load(&client->stop)) {
us_rtp_s *rtp;
if (!us_queue_get(queue, (void **)&rtp, 0.1)) {
if (rtp == NULL) {
break;
}
const int ri = us_ring_consumer_acquire(ring, 0.1);
if (ri < 0) {
continue;
}
us_rtp_s rtp;
memcpy(&rtp, ring->items[ri], sizeof(us_rtp_s));
us_ring_consumer_release(ring, ri);
if (
atomic_load(&client->transmit)
&& (video || atomic_load(&client->transmit_audio))
) {
janus_plugin_rtp packet = {0};
packet.video = rtp->video;
packet.buffer = (char *)rtp->datagram;
packet.length = rtp->used;
if (
atomic_load(&client->transmit)
&& (video || atomic_load(&client->transmit_audio))
) {
janus_plugin_rtp packet = {
.video = rtp.video,
.buffer = (char*)rtp.datagram,
.length = rtp.used,
# if JANUS_PLUGIN_API_VERSION >= 100
// The uStreamer Janus plugin places video in stream index 0 and audio
// (if available) in stream index 1.
packet.mindex = (rtp->video ? 0 : 1);
.mindex = (rtp.video ? 0 : 1),
# endif
};
janus_plugin_rtp_extensions_reset(&packet.extensions);
/*if (rtp->zero_playout_delay) {
// https://github.com/pikvm/pikvm/issues/784
packet.extensions.min_delay = 0;
packet.extensions.max_delay = 0;
} else {
packet.extensions.min_delay = 0;
// 10s - Chromium/WebRTC default
// 3s - Firefox default
packet.extensions.max_delay = 300; // == 3s, i.e. 10ms granularity
}*/
janus_plugin_rtp_extensions_reset(&packet.extensions);
/*if (rtp->zero_playout_delay) {
// https://github.com/pikvm/pikvm/issues/784
packet.extensions.min_delay = 0;
packet.extensions.max_delay = 0;
} else {
packet.extensions.min_delay = 0;
packet.extensions.max_delay = 1000;
}*/
client->gw->relay_rtp(client->session, &packet);
}
us_rtp_destroy(rtp);
client->gw->relay_rtp(client->session, &packet);
}
}
return NULL;

View File

@@ -22,20 +22,15 @@
#pragma once
#include <stdlib.h>
#include <stdbool.h>
#include <stdatomic.h>
#include <string.h>
#include <pthread.h>
#include <janus/plugins/plugin.h>
#include "uslibs/tools.h"
#include "uslibs/threading.h"
#include "uslibs/types.h"
#include "uslibs/list.h"
#include "uslibs/ring.h"
#include "logging.h"
#include "queue.h"
#include "rtp.h"
@@ -49,8 +44,8 @@ typedef struct us_janus_client_sx {
pthread_t audio_tid;
atomic_bool stop;
us_queue_s *video_queue;
us_queue_s *audio_queue;
us_ring_s *video_ring;
us_ring_s *audio_ring;
US_LIST_STRUCT(struct us_janus_client_sx);
} us_janus_client_s;

View File

@@ -22,6 +22,17 @@
#include "config.h"
#include <stdlib.h>
#include <string.h>
#include <janus/config.h>
#include <janus/plugins/plugin.h>
#include "uslibs/tools.h"
#include "const.h"
#include "logging.h"
static char *_get_value(janus_config *jcfg, const char *section, const char *option);
// static bool _get_bool(janus_config *jcfg, const char *section, const char *option, bool def);
@@ -59,13 +70,14 @@ us_config_s *us_config_init(const char *config_dir_path) {
}
goto ok;
error:
us_config_destroy(config);
config = NULL;
ok:
US_DELETE(jcfg, janus_config_destroy);
free(config_file_path);
return config;
error:
US_DELETE(config, us_config_destroy);
ok:
US_DELETE(jcfg, janus_config_destroy);
free(config_file_path);
return config;
}
void us_config_destroy(us_config_s *config) {

View File

@@ -22,17 +22,6 @@
#pragma once
#include <stdlib.h>
#include <string.h>
#include <janus/config.h>
#include <janus/plugins/plugin.h>
#include "uslibs/tools.h"
#include "const.h"
#include "logging.h"
typedef struct {
char *video_sink_name;

View File

@@ -22,13 +22,24 @@
#include "memsinkfd.h"
#include <unistd.h>
int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s* mem, uint64_t last_id) {
const long double deadline_ts = us_get_now_monotonic() + 1; // wait_timeout
long double now;
#include <linux/videodev2.h>
#include "uslibs/types.h"
#include "uslibs/tools.h"
#include "uslibs/frame.h"
#include "uslibs/memsinksh.h"
#include "logging.h"
int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s *mem, u64 last_id) {
const ldf deadline_ts = us_get_now_monotonic() + 1; // wait_timeout
ldf now_ts;
do {
const int result = us_flock_timedwait_monotonic(fd, 1); // lock_timeout
now = us_get_now_monotonic();
now_ts = us_get_now_monotonic();
if (result < 0 && errno != EWOULDBLOCK) {
US_JLOG_PERROR("video", "Can't lock memsink");
return -1;
@@ -42,12 +53,11 @@ int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s* mem, uint64_t last_id)
}
}
usleep(1000); // lock_polling
} while (now < deadline_ts);
} while (now_ts < deadline_ts);
return -2;
}
us_frame_s *us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, uint64_t *frame_id, bool key_required) {
us_frame_s *frame = us_frame_init();
int us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, us_frame_s *frame, u64 *frame_id, bool key_required) {
us_frame_set_data(frame, mem->data, mem->used);
US_FRAME_COPY_META(mem, frame);
*frame_id = mem->id;
@@ -56,18 +66,14 @@ us_frame_s *us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, uint64_t *
mem->key_requested = true;
}
bool ok = true;
bool retval = 0;
if (frame->format != V4L2_PIX_FMT_H264) {
US_JLOG_ERROR("video", "Got non-H264 frame from memsink");
ok = false;
retval = -1;
}
if (flock(fd, LOCK_UN) < 0) {
US_JLOG_PERROR("video", "Can't unlock memsink");
ok = false;
retval = -1;
}
if (!ok) {
us_frame_destroy(frame);
frame = NULL;
}
return frame;
return retval;
}

View File

@@ -22,18 +22,10 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <unistd.h>
#include <linux/videodev2.h>
#include "uslibs/tools.h"
#include "uslibs/types.h"
#include "uslibs/frame.h"
#include "uslibs/memsinksh.h"
#include "logging.h"
int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s* mem, uint64_t last_id);
us_frame_s *us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, uint64_t *frame_id, bool key_required);
int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s *mem, u64 last_id);
int us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, us_frame_s *frame, u64 *frame_id, bool key_required);

View File

@@ -20,8 +20,6 @@
*****************************************************************************/
#include <stdint.h>
#include <stdbool.h>
#include <stdatomic.h>
#include <stdlib.h>
#include <inttypes.h>
@@ -37,15 +35,16 @@
#include <janus/plugins/plugin.h>
#include <janus/rtcp.h>
#include "uslibs/types.h"
#include "uslibs/const.h"
#include "uslibs/tools.h"
#include "uslibs/threading.h"
#include "uslibs/list.h"
#include "uslibs/ring.h"
#include "uslibs/memsinksh.h"
#include "const.h"
#include "logging.h"
#include "queue.h"
#include "client.h"
#include "audio.h"
#include "tc358743.h"
@@ -56,12 +55,12 @@
#include "config.h"
static us_config_s *_g_config = NULL;
const useconds_t _g_watchers_polling = 100000;
static us_config_s *_g_config = NULL;
static const useconds_t _g_watchers_polling = 100000;
static us_janus_client_s *_g_clients = NULL;
static janus_callbacks *_g_gw = NULL;
static us_queue_s *_g_video_queue = NULL;
static us_ring_s *_g_video_ring = NULL;
static us_rtpv_s *_g_rtpv = NULL;
static us_rtpa_s *_g_rtpa = NULL;
@@ -99,27 +98,34 @@ static atomic_bool _g_key_required = false;
janus_plugin *create(void);
static void *_video_rtp_thread(UNUSED void *arg) {
static void *_video_rtp_thread(void *arg) {
(void)arg;
US_THREAD_RENAME("us_video_rtp");
atomic_store(&_g_video_rtp_tid_created, true);
while (!_STOP) {
us_frame_s *frame;
if (us_queue_get(_g_video_queue, (void **)&frame, 0.1) == 0) {
const int ri = us_ring_consumer_acquire(_g_video_ring, 0.1);
if (ri >= 0) {
const us_frame_s *const frame = _g_video_ring->items[ri];
_LOCK_VIDEO;
us_rtpv_wrap(_g_rtpv, frame);
const bool zero_playout_delay = (frame->gop == 0);
us_rtpv_wrap(_g_rtpv, frame, zero_playout_delay);
_UNLOCK_VIDEO;
us_frame_destroy(frame);
us_ring_consumer_release(_g_video_ring, ri);
}
}
return NULL;
}
static void *_video_sink_thread(UNUSED void *arg) {
static void *_video_sink_thread(void *arg) {
(void)arg;
US_THREAD_RENAME("us_video_sink");
atomic_store(&_g_video_sink_tid_created, true);
uint64_t frame_id = 0;
us_frame_s *drop = us_frame_init();
u64 frame_id = 0;
int once = 0;
while (!_STOP) {
@@ -146,38 +152,46 @@ static void *_video_sink_thread(UNUSED void *arg) {
US_JLOG_INFO("video", "Memsink opened; reading frames ...");
while (!_STOP && _HAS_WATCHERS) {
const int result = us_memsink_fd_wait_frame(fd, mem, frame_id);
if (result == 0) {
us_frame_s *const frame = us_memsink_fd_get_frame(fd, mem, &frame_id, atomic_load(&_g_key_required));
if (frame == NULL) {
const int waited = us_memsink_fd_wait_frame(fd, mem, frame_id);
if (waited == 0) {
const int ri = us_ring_producer_acquire(_g_video_ring, 0);
us_frame_s *frame;
if (ri >= 0) {
frame = _g_video_ring->items[ri];
} else {
US_ONCE({ US_JLOG_PERROR("video", "Video ring is full"); });
frame = drop;
}
const int got = us_memsink_fd_get_frame(fd, mem, frame, &frame_id, atomic_load(&_g_key_required));
if (ri >= 0) {
us_ring_producer_release(_g_video_ring, ri);
}
if (got < 0) {
goto close_memsink;
}
if (frame->key) {
if (ri >= 0 && frame->key) {
atomic_store(&_g_key_required, false);
}
if (us_queue_put(_g_video_queue, frame, 0) != 0) {
US_ONCE({ US_JLOG_PERROR("video", "Video queue is full"); });
us_frame_destroy(frame);
}
} else if (result == -1) {
} else if (waited != -2) {
goto close_memsink;
}
}
close_memsink:
if (mem != NULL) {
US_JLOG_INFO("video", "Memsink closed");
us_memsink_shared_unmap(mem);
}
if (fd >= 0) {
close(fd);
}
sleep(1); // error_delay
close_memsink:
US_DELETE(mem, us_memsink_shared_unmap);
US_CLOSE_FD(fd);
US_JLOG_INFO("video", "Memsink closed");
sleep(1); // error_delay
}
us_frame_destroy(drop);
return NULL;
}
static void *_audio_thread(UNUSED void *arg) {
static void *_audio_thread(void *arg) {
(void)arg;
US_THREAD_RENAME("us_audio");
atomic_store(&_g_audio_tid_created, true);
assert(_g_config->audio_dev_name != NULL);
@@ -217,9 +231,9 @@ static void *_audio_thread(UNUSED void *arg) {
goto close_audio;
}
size_t size = US_RTP_DATAGRAM_SIZE - US_RTP_HEADER_SIZE;
uint8_t data[size];
uint64_t pts;
uz size = US_RTP_DATAGRAM_SIZE - US_RTP_HEADER_SIZE;
u8 data[size];
u64 pts;
const int result = us_audio_get_encoded(audio, data, &size, &pts);
if (result == 0) {
_LOCK_AUDIO;
@@ -230,9 +244,9 @@ static void *_audio_thread(UNUSED void *arg) {
}
}
close_audio:
US_DELETE(audio, us_audio_destroy);
sleep(1); // error_delay
close_audio:
US_DELETE(audio, us_audio_destroy);
sleep(1); // error_delay
}
return NULL;
}
@@ -256,7 +270,7 @@ static int _plugin_init(janus_callbacks *gw, const char *config_dir_path) {
}
_g_gw = gw;
_g_video_queue = us_queue_init(1024);
US_RING_INIT_WITH_ITEMS(_g_video_ring, 64, us_frame_init);
_g_rtpv = us_rtpv_init(_relay_rtp_clients);
if (_g_config->audio_dev_name != NULL && us_audio_probe(_g_config->audio_dev_name)) {
_g_rtpa = us_rtpa_init(_relay_rtp_clients);
@@ -284,7 +298,7 @@ static void _plugin_destroy(void) {
us_janus_client_destroy(client);
});
US_QUEUE_DELETE_WITH_ITEMS(_g_video_queue, us_frame_destroy);
US_RING_DELETE_WITH_ITEMS(_g_video_ring, us_frame_destroy);
US_DELETE(_g_rtpa, us_rtpa_destroy);
US_DELETE(_g_rtpv, us_rtpv_destroy);
@@ -343,7 +357,8 @@ static json_t *_plugin_query_session(janus_plugin_session *session) {
return info;
}
static void _set_transmit(janus_plugin_session *session, UNUSED const char *msg, bool transmit) {
static void _set_transmit(janus_plugin_session *session, const char *msg, bool transmit) {
(void)msg;
_IF_DISABLED({ return; });
_LOCK_ALL;
bool found = false;
@@ -493,16 +508,18 @@ static struct janus_plugin_result *_plugin_handle_message(
PUSH_ERROR(405, "Not implemented");
}
ok_wait:
FREE_MSG_JSEP;
return janus_plugin_result_new(JANUS_PLUGIN_OK_WAIT, NULL, NULL);
ok_wait:
FREE_MSG_JSEP;
return janus_plugin_result_new(JANUS_PLUGIN_OK_WAIT, NULL, NULL);
# undef PUSH_STATUS
# undef PUSH_ERROR
# undef FREE_MSG_JSEP
}
static void _plugin_incoming_rtcp(UNUSED janus_plugin_session *handle, UNUSED janus_plugin_rtcp *packet) {
static void _plugin_incoming_rtcp(janus_plugin_session *handle, janus_plugin_rtcp *packet) {
(void)handle;
(void)packet;
if (packet->video && janus_rtcp_has_pli(packet->buffer, packet->length)) {
// US_JLOG_INFO("main", "Got video PLI");
atomic_store(&_g_key_required, true);

View File

@@ -25,29 +25,30 @@
#include "rtp.h"
#include <stdlib.h>
us_rtp_s *us_rtp_init(unsigned payload, bool video) {
#include "uslibs/types.h"
#include "uslibs/tools.h"
us_rtp_s *us_rtp_init(void) {
us_rtp_s *rtp;
US_CALLOC(rtp, 1);
rtp->payload = payload;
rtp->video = video;
rtp->ssrc = us_triple_u32(us_get_now_monotonic_u64());
return rtp;
}
us_rtp_s *us_rtp_dup(const us_rtp_s *rtp) {
us_rtp_s *new;
US_CALLOC(new, 1);
memcpy(new, rtp, sizeof(us_rtp_s));
return new;
}
void us_rtp_destroy(us_rtp_s *rtp) {
free(rtp);
}
void us_rtp_write_header(us_rtp_s *rtp, uint32_t pts, bool marked) {
uint32_t word0 = 0x80000000;
void us_rtp_assign(us_rtp_s *rtp, uint payload, bool video) {
rtp->payload = payload;
rtp->video = video;
rtp->ssrc = us_triple_u32(us_get_now_monotonic_u64());
}
void us_rtp_write_header(us_rtp_s *rtp, u32 pts, bool marked) {
u32 word0 = 0x80000000;
if (marked) {
word0 |= 1 << 23;
}
@@ -55,7 +56,8 @@ void us_rtp_write_header(us_rtp_s *rtp, uint32_t pts, bool marked) {
word0 |= rtp->seq;
++rtp->seq;
# define WRITE_BE_U32(_offset, _value) *((uint32_t *)(rtp->datagram + _offset)) = __builtin_bswap32(_value)
# define WRITE_BE_U32(x_offset, x_value) \
*((u32 *)(rtp->datagram + x_offset)) = __builtin_bswap32(x_value)
WRITE_BE_U32(0, word0);
WRITE_BE_U32(4, pts);
WRITE_BE_U32(8, rtp->ssrc);

View File

@@ -22,13 +22,7 @@
#pragma once
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <sys/types.h>
#include "uslibs/tools.h"
#include "uslibs/types.h"
// https://stackoverflow.com/questions/47635545/why-webrtc-chose-rtp-max-packet-size-to-1200-bytes
@@ -37,21 +31,21 @@
typedef struct {
unsigned payload;
bool video;
uint32_t ssrc;
uint payload;
bool video;
u32 ssrc;
uint16_t seq;
uint8_t datagram[US_RTP_DATAGRAM_SIZE];
size_t used;
bool zero_playout_delay;
u16 seq;
u8 datagram[US_RTP_DATAGRAM_SIZE];
uz used;
bool zero_playout_delay;
} us_rtp_s;
typedef void (*us_rtp_callback_f)(const us_rtp_s *rtp);
us_rtp_s *us_rtp_init(unsigned payload, bool video);
us_rtp_s *us_rtp_dup(const us_rtp_s *rtp);
us_rtp_s *us_rtp_init(void);
void us_rtp_destroy(us_rtp_s *rtp);
void us_rtp_write_header(us_rtp_s *rtp, uint32_t pts, bool marked);
void us_rtp_assign(us_rtp_s *rtp, uint payload, bool video);
void us_rtp_write_header(us_rtp_s *rtp, u32 pts, bool marked);

View File

@@ -22,11 +22,18 @@
#include "rtpa.h"
#include <stdlib.h>
#include <inttypes.h>
#include "uslibs/types.h"
#include "uslibs/tools.h"
us_rtpa_s *us_rtpa_init(us_rtp_callback_f callback) {
us_rtpa_s *rtpa;
US_CALLOC(rtpa, 1);
rtpa->rtp = us_rtp_init(111, false);
rtpa->rtp = us_rtp_init();
us_rtp_assign(rtpa->rtp, 111, false);
rtpa->callback = callback;
return rtpa;
}
@@ -37,7 +44,7 @@ void us_rtpa_destroy(us_rtpa_s *rtpa) {
}
char *us_rtpa_make_sdp(us_rtpa_s *rtpa) {
# define PAYLOAD rtpa->rtp->payload
const uint pl = rtpa->rtp->payload;
char *sdp;
US_ASPRINTF(sdp,
"m=audio 1 RTP/SAVPF %u" RN
@@ -49,14 +56,13 @@ char *us_rtpa_make_sdp(us_rtpa_s *rtpa) {
"a=rtcp-fb:%u goog-remb" RN
"a=ssrc:%" PRIu32 " cname:ustreamer" RN
"a=sendonly" RN,
PAYLOAD, PAYLOAD, PAYLOAD, PAYLOAD, PAYLOAD, // PAYLOAD,
pl, pl, pl, pl, pl, // pl,
rtpa->rtp->ssrc
);
# undef PAYLOAD
return sdp;
}
void us_rtpa_wrap(us_rtpa_s *rtpa, const uint8_t *data, size_t size, uint32_t pts) {
void us_rtpa_wrap(us_rtpa_s *rtpa, const u8 *data, uz size, u32 pts) {
if (size + US_RTP_HEADER_SIZE <= US_RTP_DATAGRAM_SIZE) {
us_rtp_write_header(rtpa->rtp, pts, false);
memcpy(rtpa->rtp->datagram + US_RTP_HEADER_SIZE, data, size);

View File

@@ -22,15 +22,7 @@
#pragma once
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <inttypes.h>
#include <sys/types.h>
#include "uslibs/tools.h"
#include "uslibs/threading.h"
#include "uslibs/types.h"
#include "rtp.h"
@@ -45,4 +37,4 @@ us_rtpa_s *us_rtpa_init(us_rtp_callback_f callback);
void us_rtpa_destroy(us_rtpa_s *rtpa);
char *us_rtpa_make_sdp(us_rtpa_s *rtpa);
void us_rtpa_wrap(us_rtpa_s *rtpa, const uint8_t *data, size_t size, uint32_t pts);
void us_rtpa_wrap(us_rtpa_s *rtpa, const u8 *data, uz size, u32 pts);

View File

@@ -25,16 +25,27 @@
#include "rtpv.h"
#include <stdlib.h>
#include <inttypes.h>
#include <assert.h>
void _rtpv_process_nalu(us_rtpv_s *rtpv, const uint8_t *data, size_t size, uint32_t pts, bool marked);
#include <linux/videodev2.h>
static ssize_t _find_annexb(const uint8_t *data, size_t size);
#include "uslibs/types.h"
#include "uslibs/tools.h"
#include "uslibs/frame.h"
void _rtpv_process_nalu(us_rtpv_s *rtpv, const u8 *data, uz size, u32 pts, bool marked);
static sz _find_annexb(const u8 *data, uz size);
us_rtpv_s *us_rtpv_init(us_rtp_callback_f callback) {
us_rtpv_s *rtpv;
US_CALLOC(rtpv, 1);
rtpv->rtp = us_rtp_init(96, true);
rtpv->rtp = us_rtp_init();
us_rtp_assign(rtpv->rtp, 96, true);
rtpv->callback = callback;
return rtpv;
}
@@ -45,9 +56,9 @@ void us_rtpv_destroy(us_rtpv_s *rtpv) {
}
char *us_rtpv_make_sdp(us_rtpv_s *rtpv) {
# define PAYLOAD rtpv->rtp->payload
// https://tools.ietf.org/html/rfc6184
// https://github.com/meetecho/janus-gateway/issues/2443
const uint pl = rtpv->rtp->payload;
char *sdp;
US_ASPRINTF(sdp,
"m=video 1 RTP/SAVPF %u" RN
@@ -61,38 +72,37 @@ char *us_rtpv_make_sdp(us_rtpv_s *rtpv) {
"a=ssrc:%" PRIu32 " cname:ustreamer" RN
"a=extmap:1 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay" RN
"a=sendonly" RN,
PAYLOAD, PAYLOAD, PAYLOAD, PAYLOAD,
PAYLOAD, PAYLOAD, PAYLOAD,
pl, pl, pl, pl,
pl, pl, pl,
rtpv->rtp->ssrc
);
return sdp;
# undef PAYLOAD
}
#define _PRE 3 // Annex B prefix length
void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame) {
void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame, bool zero_playout_delay) {
// There is a complicated logic here but everything works as it should:
// - https://github.com/pikvm/ustreamer/issues/115#issuecomment-893071775
assert(frame->format == V4L2_PIX_FMT_H264);
rtpv->rtp->zero_playout_delay = (frame->gop == 0);
rtpv->rtp->zero_playout_delay = zero_playout_delay;
const uint32_t pts = us_get_now_monotonic_u64() * 9 / 100; // PTS units are in 90 kHz
ssize_t last_offset = -_PRE;
const u32 pts = us_get_now_monotonic_u64() * 9 / 100; // PTS units are in 90 kHz
sz last_offset = -_PRE;
while (true) { // Find and iterate by nalus
const size_t next_start = last_offset + _PRE;
ssize_t offset = _find_annexb(frame->data + next_start, frame->used - next_start);
const uz next_start = last_offset + _PRE;
sz offset = _find_annexb(frame->data + next_start, frame->used - next_start);
if (offset < 0) {
break;
}
offset += next_start;
if (last_offset >= 0) {
const uint8_t *const data = frame->data + last_offset + _PRE;
size_t size = offset - last_offset - _PRE;
const u8 *const data = frame->data + last_offset + _PRE;
uz size = offset - last_offset - _PRE;
if (data[size - 1] == 0) { // Check for extra 00
--size;
}
@@ -103,34 +113,33 @@ void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame) {
}
if (last_offset >= 0) {
const uint8_t *const data = frame->data + last_offset + _PRE;
size_t size = frame->used - last_offset - _PRE;
const u8 *const data = frame->data + last_offset + _PRE;
uz size = frame->used - last_offset - _PRE;
_rtpv_process_nalu(rtpv, data, size, pts, true);
}
}
void _rtpv_process_nalu(us_rtpv_s *rtpv, const uint8_t *data, size_t size, uint32_t pts, bool marked) {
# define DG rtpv->rtp->datagram
const unsigned ref_idc = (data[0] >> 5) & 3;
const unsigned type = data[0] & 0x1F;
void _rtpv_process_nalu(us_rtpv_s *rtpv, const u8 *data, uz size, u32 pts, bool marked) {
const uint ref_idc = (data[0] >> 5) & 3;
const uint type = data[0] & 0x1F;
u8 *dg = rtpv->rtp->datagram;
if (size + US_RTP_HEADER_SIZE <= US_RTP_DATAGRAM_SIZE) {
us_rtp_write_header(rtpv->rtp, pts, marked);
memcpy(DG + US_RTP_HEADER_SIZE, data, size);
memcpy(dg + US_RTP_HEADER_SIZE, data, size);
rtpv->rtp->used = size + US_RTP_HEADER_SIZE;
rtpv->callback(rtpv->rtp);
return;
}
const size_t fu_overhead = US_RTP_HEADER_SIZE + 2; // FU-A overhead
const uz fu_overhead = US_RTP_HEADER_SIZE + 2; // FU-A overhead
const uint8_t *src = data + 1;
ssize_t remaining = size - 1;
const u8 *src = data + 1;
sz remaining = size - 1;
bool first = true;
while (remaining > 0) {
ssize_t frag_size = US_RTP_DATAGRAM_SIZE - fu_overhead;
sz frag_size = US_RTP_DATAGRAM_SIZE - fu_overhead;
const bool last = (remaining <= frag_size);
if (last) {
frag_size = remaining;
@@ -138,18 +147,18 @@ void _rtpv_process_nalu(us_rtpv_s *rtpv, const uint8_t *data, size_t size, uint3
us_rtp_write_header(rtpv->rtp, pts, (marked && last));
DG[US_RTP_HEADER_SIZE] = 28 | (ref_idc << 5);
dg[US_RTP_HEADER_SIZE] = 28 | (ref_idc << 5);
uint8_t fu = type;
u8 fu = type;
if (first) {
fu |= 0x80;
}
if (last) {
fu |= 0x40;
}
DG[US_RTP_HEADER_SIZE + 1] = fu;
dg[US_RTP_HEADER_SIZE + 1] = fu;
memcpy(DG + fu_overhead, src, frag_size);
memcpy(dg + fu_overhead, src, frag_size);
rtpv->rtp->used = fu_overhead + frag_size;
rtpv->callback(rtpv->rtp);
@@ -157,14 +166,12 @@ void _rtpv_process_nalu(us_rtpv_s *rtpv, const uint8_t *data, size_t size, uint3
remaining -= frag_size;
first = false;
}
# undef DG
}
static ssize_t _find_annexb(const uint8_t *data, size_t size) {
static sz _find_annexb(const u8 *data, uz size) {
// Parses buffer for 00 00 01 start codes
if (size >= _PRE) {
for (size_t index = 0; index <= size - _PRE; ++index) {
for (uz index = 0; index <= size - _PRE; ++index) {
if (data[index] == 0 && data[index + 1] == 0 && data[index + 2] == 1) {
return index;
}

View File

@@ -22,16 +22,7 @@
#pragma once
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <inttypes.h>
#include <assert.h>
#include <sys/types.h>
#include <linux/videodev2.h>
#include "uslibs/tools.h"
#include "uslibs/types.h"
#include "uslibs/frame.h"
#include "rtp.h"
@@ -47,4 +38,4 @@ us_rtpv_s *us_rtpv_init(us_rtp_callback_f callback);
void us_rtpv_destroy(us_rtpv_s *rtpv);
char *us_rtpv_make_sdp(us_rtpv_s *rtpv);
void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame);
void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame, bool zero_playout_delay);

View File

@@ -22,6 +22,18 @@
#include "tc358743.h"
#include <unistd.h>
#include <fcntl.h>
#include <linux/videodev2.h>
#include <linux/v4l2-controls.h>
#include "uslibs/types.h"
#include "uslibs/tools.h"
#include "uslibs/xioctl.h"
#include "logging.h"
#ifndef V4L2_CID_USER_TC358743_BASE
# define V4L2_CID_USER_TC358743_BASE (V4L2_CID_USER_BASE + 0x1080)
@@ -44,8 +56,7 @@ int us_tc358743_read_info(const char *path, us_tc358743_info_s *info) {
}
# define READ_CID(x_cid, x_field) { \
struct v4l2_control m_ctl = {0}; \
m_ctl.id = x_cid; \
struct v4l2_control m_ctl = {.id = x_cid}; \
if (us_xioctl(fd, VIDIOC_G_CTRL, &m_ctl) < 0) { \
US_JLOG_PERROR("audio", "Can't get value of " #x_cid); \
close(fd); \
@@ -53,10 +64,8 @@ int us_tc358743_read_info(const char *path, us_tc358743_info_s *info) {
} \
info->x_field = m_ctl.value; \
}
READ_CID(TC358743_CID_AUDIO_PRESENT, has_audio);
READ_CID(TC358743_CID_AUDIO_SAMPLING_RATE, audio_hz);
# undef READ_CID
close(fd);

View File

@@ -22,24 +22,12 @@
#pragma once
#include <unistd.h>
#include <fcntl.h>
#include <stdbool.h>
#include <sys/types.h>
#include <linux/videodev2.h>
#include <linux/v4l2-controls.h>
#include "uslibs/tools.h"
#include "uslibs/xioctl.h"
#include "logging.h"
#include "uslibs/types.h"
typedef struct {
bool has_audio;
unsigned audio_hz;
bool has_audio;
uint audio_hz;
} us_tc358743_info_s;

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

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

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

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

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

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

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

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

44
janus/src/uslibs/types.h Normal file
View File

@@ -0,0 +1,44 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
typedef long long sll;
typedef ssize_t sz;
typedef int8_t s8;
typedef int16_t s16;
typedef int32_t s32;
typedef int64_t s64;
typedef unsigned uint;
typedef unsigned long long ull;
typedef size_t uz;
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
typedef long double ldf;

View File

@@ -2,5 +2,3 @@
#define WITH_GPIO
#define JANUS_PLUGIN_INIT(...) { __VA_ARGS__ }
#define EVTHREAD_USE_PTHREADS_IMPLEMENTED 1
#define CLOCK_MONOTONIC_RAW 1
#define CLOCK_MONOTONIC_FAST 1

View File

@@ -33,7 +33,7 @@ max-line-length = 160
[BASIC]
# Good variable names which should always be accepted, separated by a comma
good-names = _, __, x, y, ws, make-html-h, make-jpeg-h, make-ico-h
good-names = _, __, x, y, ws, make-html-h, make-ico-h
# Regular expression matching correct method names
method-rgx = [a-z_][a-z0-9_]{2,50}$

View File

@@ -9,18 +9,17 @@ changedir = /src
[testenv:cppcheck]
allowlist_externals = cppcheck
commands = cppcheck \
-j4 \
--force \
--std=c17 \
--error-exitcode=1 \
--quiet \
--enable=warning,unusedFunction,portability,performance,style \
--enable=warning,portability,performance,style \
--suppress=assignmentInAssert \
--suppress=variableScope \
--inline-suppr \
--library=python \
--include=linters/cppcheck.h \
src python/*.? janus/*.?
src python/src/*.? janus/src/*.?
[testenv:flake8]
allowlist_externals = bash

View File

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

View File

@@ -1,6 +1,6 @@
.\" Manpage for ustreamer.
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
.TH USTREAMER 1 "version 5.40" "November 2020"
.TH USTREAMER 1 "version 5.57" "November 2020"
.SH NAME
ustreamer \- stream MJPEG video from any V4L2 device to the network
@@ -52,7 +52,7 @@ Initial image resolution. Default: 640x480.
.TP
.BR \-m\ \fIfmt ", " \-\-format\ \fIfmt
Image format.
Available: YUYV, UYVY, RGB565, RGB24, JPEG; default: YUYV.
Available: YUYV, YVYU, UYVY, RGB565, RGB24, JPEG; default: YUYV.
.TP
.BR \-a\ \fIstd ", " \-\-tv\-standard\ \fIstd
Force TV standard.
@@ -103,10 +103,10 @@ NOOP ─ Don't compress MJPEG stream (do nothing).
It doesn't do anything. Still here for compatibility.
.TP
.BR \-k\ \fIpath ", " \-\-blank\ \fIpath
Path to JPEG file that will be shown when the device is disconnected during the streaming. Default: black screen 640x480 with 'NO SIGNAL'.
It doesn't do anything. Still here for compatibility.
.TP
.BR \-K\ \fIsec ", " \-\-last\-as\-blank\ \fIsec
Show the last frame received from the camera after it was disconnected, but no more than specified time (or endlessly if 0 is specified). If the device has not yet been online, display 'NO SIGNAL' or the image specified by option \-\-blank. Note: currently this option has no effect on memory sinks. Default: disabled.
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 some error message. 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.
@@ -248,7 +248,7 @@ Timeout for lock. Default: 1.
H264 bitrate in Kbps. Default: 5000.
.TP
.BR \-\-h264\-gop\ \fIN
Intarval between keyframes. Default: 30.
Interval between keyframes. Default: 30.
.TP
.BR \-\-h264\-m2m\-device\ \fI/dev/path
Path to V4L2 mem-to-mem encoder device. Default: auto-select.

View File

@@ -3,7 +3,7 @@
pkgname=ustreamer
pkgver=5.40
pkgver=5.57
pkgrel=1
pkgdesc="Lightweight and fast MJPEG-HTTP streamer"
url="https://github.com/pikvm/ustreamer"

View File

@@ -6,7 +6,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=ustreamer
PKG_VERSION:=5.40
PKG_VERSION:=5.57
PKG_RELEASE:=1
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>

View File

@@ -9,7 +9,7 @@ PY ?= python3
# =====
all:
$(info == PY_BUILD ustreamer-*.so)
@ $(PY) setup.py build
$(ECHO) $(PY) setup.py build
install:
@@ -17,4 +17,4 @@ install:
clean:
rm -rf build
rm -rf build ustreamer.egg-info

View File

@@ -17,7 +17,7 @@ def _find_sources(suffix: str) -> list[str]:
if __name__ == "__main__":
setup(
name="ustreamer",
version="5.40",
version="5.57",
description="uStreamer tools",
author="Maxim Devaev",
author_email="mdevaev@gmail.com",

44
python/src/uslibs/types.h Normal file
View File

@@ -0,0 +1,44 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
typedef long long sll;
typedef ssize_t sz;
typedef int8_t s8;
typedef int16_t s16;
typedef int32_t s32;
typedef int64_t s64;
typedef unsigned uint;
typedef unsigned long long ull;
typedef size_t uz;
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
typedef long double ldf;

View File

@@ -13,6 +13,7 @@
#include <Python.h>
#include "uslibs/types.h"
#include "uslibs/tools.h"
#include "uslibs/frame.h"
#include "uslibs/memsinksh.h"
@@ -29,29 +30,16 @@ typedef struct {
int fd;
us_memsink_shared_s *mem;
uint64_t frame_id;
long double frame_ts;
u64 frame_id;
ldf frame_ts;
us_frame_s *frame;
} _MemsinkObject;
#define _MEM(x_next) self->mem->x_next
#define _FRAME(x_next) self->frame->x_next
static void _MemsinkObject_destroy_internals(_MemsinkObject *self) {
if (self->mem != NULL) {
us_memsink_shared_unmap(self->mem);
self->mem = NULL;
}
if (self->fd >= 0) {
close(self->fd);
self->fd = -1;
}
if (self->frame != NULL) {
us_frame_destroy(self->frame);
self->frame = NULL;
}
US_DELETE(self->mem, us_memsink_shared_unmap);
US_CLOSE_FD(self->fd);
US_DELETE(self->frame, us_frame_destroy);
}
static int _MemsinkObject_init(_MemsinkObject *self, PyObject *args, PyObject *kwargs) {
@@ -65,17 +53,15 @@ static int _MemsinkObject_init(_MemsinkObject *self, PyObject *args, PyObject *k
return -1;
}
# define SET_DOUBLE(_field, _cond) { \
if (!(self->_field _cond)) { \
PyErr_SetString(PyExc_ValueError, #_field " must be " #_cond); \
# define SET_DOUBLE(x_field, x_cond) { \
if (!(self->x_field x_cond)) { \
PyErr_SetString(PyExc_ValueError, #x_field " must be " #x_cond); \
return -1; \
} \
}
SET_DOUBLE(lock_timeout, > 0);
SET_DOUBLE(wait_timeout, > 0);
SET_DOUBLE(drop_same_frames, >= 0);
# undef SET_DOUBLE
self->frame = us_frame_init();
@@ -84,22 +70,20 @@ static int _MemsinkObject_init(_MemsinkObject *self, PyObject *args, PyObject *k
PyErr_SetFromErrno(PyExc_OSError);
goto error;
}
if ((self->mem = us_memsink_shared_map(self->fd)) == NULL) {
PyErr_SetFromErrno(PyExc_OSError);
goto error;
}
return 0;
error:
_MemsinkObject_destroy_internals(self);
return -1;
error:
_MemsinkObject_destroy_internals(self);
return -1;
}
static PyObject *_MemsinkObject_repr(_MemsinkObject *self) {
char repr[1024];
snprintf(repr, 1023, "<Memsink(%s)>", self->obj);
US_SNPRINTF(repr, 1023, "<Memsink(%s)>", self->obj);
return Py_BuildValue("s", repr);
}
@@ -123,7 +107,7 @@ static PyObject *_MemsinkObject_exit(_MemsinkObject *self, PyObject *Py_UNUSED(i
}
static int _wait_frame(_MemsinkObject *self) {
const long double deadline_ts = us_get_now_monotonic() + self->wait_timeout;
const ldf deadline_ts = us_get_now_monotonic() + self->wait_timeout;
# define RETURN_OS_ERROR { \
Py_BLOCK_THREADS \
@@ -131,25 +115,26 @@ static int _wait_frame(_MemsinkObject *self) {
return -1; \
}
long double now;
ldf now_ts;
do {
Py_BEGIN_ALLOW_THREADS
const int retval = us_flock_timedwait_monotonic(self->fd, self->lock_timeout);
now = us_get_now_monotonic();
now_ts = us_get_now_monotonic();
if (retval < 0 && errno != EWOULDBLOCK) {
RETURN_OS_ERROR;
} else if (retval == 0) {
if (_MEM(magic) == US_MEMSINK_MAGIC && _MEM(version) == US_MEMSINK_VERSION && _MEM(id) != self->frame_id) {
us_memsink_shared_s *mem = self->mem;
if (mem->magic == US_MEMSINK_MAGIC && mem->version == US_MEMSINK_VERSION && mem->id != self->frame_id) {
if (self->drop_same_frames > 0) {
if (
US_FRAME_COMPARE_META_USED_NOTS(self->mem, self->frame)
&& (self->frame_ts + self->drop_same_frames > now)
&& !memcmp(_FRAME(data), _MEM(data), _MEM(used))
US_FRAME_COMPARE_GEOMETRY(self->mem, self->frame)
&& (self->frame_ts + self->drop_same_frames > now_ts)
&& !memcmp(self->frame->data, mem->data, mem->used)
) {
self->frame_id = _MEM(id);
self->frame_id = mem->id;
goto drop;
}
}
@@ -163,22 +148,18 @@ static int _wait_frame(_MemsinkObject *self) {
}
}
drop:
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
} while (now_ts < deadline_ts);
return -2;
# undef RETURN_OS_ERROR
}
static PyObject *_MemsinkObject_wait_frame(_MemsinkObject *self, PyObject *args, PyObject *kwargs) {
@@ -199,13 +180,14 @@ static PyObject *_MemsinkObject_wait_frame(_MemsinkObject *self, PyObject *args,
default: return NULL;
}
us_frame_set_data(self->frame, _MEM(data), _MEM(used));
us_memsink_shared_s *mem = self->mem;
us_frame_set_data(self->frame, mem->data, mem->used);
US_FRAME_COPY_META(self->mem, self->frame);
self->frame_id = _MEM(id);
self->frame_id = mem->id;
self->frame_ts = us_get_now_monotonic();
_MEM(last_client_ts) = self->frame_ts;
mem->last_client_ts = self->frame_ts;
if (key_required) {
_MEM(key_requested) = true;
mem->key_requested = true;
}
if (flock(self->fd, LOCK_UN) < 0) {
@@ -217,18 +199,18 @@ static PyObject *_MemsinkObject_wait_frame(_MemsinkObject *self, PyObject *args,
return NULL;
}
# define SET_VALUE(_key, _maker) { \
PyObject *_tmp = _maker; \
if (_tmp == NULL) { \
# define SET_VALUE(x_key, x_maker) { \
PyObject *m_tmp = x_maker; \
if (m_tmp == NULL) { \
return NULL; \
} \
if (PyDict_SetItemString(dict_frame, _key, _tmp) < 0) { \
Py_DECREF(_tmp); \
if (PyDict_SetItemString(dict_frame, x_key, m_tmp) < 0) { \
Py_DECREF(m_tmp); \
return NULL; \
} \
Py_DECREF(_tmp); \
Py_DECREF(m_tmp); \
}
# define SET_NUMBER(_key, _from, _to) SET_VALUE(#_key, Py##_to##_From##_from(_FRAME(_key)))
# define SET_NUMBER(x_key, x_from, x_to) SET_VALUE(#x_key, Py##x_to##_From##x_from(self->frame->x_key))
SET_NUMBER(width, Long, Long);
SET_NUMBER(height, Long, Long);
@@ -240,7 +222,7 @@ static PyObject *_MemsinkObject_wait_frame(_MemsinkObject *self, PyObject *args,
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)));
SET_VALUE("data", PyBytes_FromStringAndSize((const char *)self->frame->data, self->frame->used));
# undef SET_NUMBER
# undef SET_VALUE
@@ -252,21 +234,19 @@ static PyObject *_MemsinkObject_is_opened(_MemsinkObject *self, PyObject *Py_UNU
return PyBool_FromLong(self->mem != NULL && self->fd > 0);
}
#define FIELD_GETTER(_field, _from, _to) \
static PyObject *_MemsinkObject_getter_##_field(_MemsinkObject *self, void *Py_UNUSED(closure)) { \
return Py##_to##_From##_from(self->_field); \
#define FIELD_GETTER(x_field, x_from, x_to) \
static PyObject *_MemsinkObject_getter_##x_field(_MemsinkObject *self, void *Py_UNUSED(closure)) { \
return Py##x_to##_From##x_from(self->x_field); \
}
FIELD_GETTER(obj, String, Unicode)
FIELD_GETTER(lock_timeout, Double, Float)
FIELD_GETTER(wait_timeout, Double, Float)
FIELD_GETTER(drop_same_frames, Double, Float)
#undef FIELD_GETTER
static PyMethodDef _MemsinkObject_methods[] = {
# define ADD_METHOD(_name, _method, _flags) \
{.ml_name = _name, .ml_meth = (PyCFunction)_MemsinkObject_##_method, .ml_flags = (_flags)}
# define ADD_METHOD(x_name, x_method, x_flags) \
{.ml_name = x_name, .ml_meth = (PyCFunction)_MemsinkObject_##x_method, .ml_flags = (x_flags)}
ADD_METHOD("close", close, METH_NOARGS),
ADD_METHOD("__enter__", enter, METH_NOARGS),
ADD_METHOD("__exit__", exit, METH_VARARGS),
@@ -277,7 +257,7 @@ static PyMethodDef _MemsinkObject_methods[] = {
};
static PyGetSetDef _MemsinkObject_getsets[] = {
# define ADD_GETTER(_field) {.name = #_field, .get = (getter)_MemsinkObject_getter_##_field}
# define ADD_GETTER(x_field) {.name = #x_field, .get = (getter)_MemsinkObject_getter_##x_field}
ADD_GETTER(obj),
ADD_GETTER(lock_timeout),
ADD_GETTER(wait_timeout),
@@ -305,7 +285,7 @@ static PyModuleDef _Module = {
.m_size = -1,
};
PyMODINIT_FUNC PyInit_ustreamer(void) { // cppcheck-suppress unusedFunction
PyMODINIT_FUNC PyInit_ustreamer(void) {
PyObject *module = PyModule_Create(&_Module);
if (module == NULL) {
return NULL;

View File

@@ -9,6 +9,7 @@ LDFLAGS ?=
# =====
_USTR = ustreamer.bin
_DUMP = ustreamer-dump.bin
_V4P = ustreamer-v4p.bin
_CFLAGS = -MD -c -std=c17 -Wall -Wextra -D_GNU_SOURCE $(CFLAGS)
_LDFLAGS = $(LDFLAGS)
@@ -32,9 +33,19 @@ _DUMP_SRCS = $(shell ls \
dump/*.c \
)
_V4P_LIBS = $(_COMMON_LIBS)
_V4P_SRCS = $(shell ls \
libs/*.c \
v4p/*.c \
)
_BUILD = build
_TARGETS = $(_USTR) $(_DUMP)
_OBJS = $(_USTR_SRCS:%.c=$(_BUILD)/%.o) $(_DUMP_SRCS:%.c=$(_BUILD)/%.o)
define optbool
$(filter $(shell echo $(1) | tr A-Z a-z), yes on 1)
endef
@@ -42,7 +53,7 @@ endef
ifneq ($(call optbool,$(WITH_GPIO)),)
_USTR_LIBS += -lgpiod
override _CFLAGS += -DWITH_GPIO
override _CFLAGS += -DWITH_GPIO $(shell pkg-config --atleast-version=2 libgpiod 2> /dev/null && echo -DHAVE_GPIOD2)
_USTR_SRCS += $(shell ls ustreamer/gpio/*.c)
endif
@@ -69,40 +80,53 @@ override _CFLAGS += -DWITH_SETPROCTITLE
endif
WITH_V4P ?= 0
ifneq ($(call optbool,$(WITH_V4P)),)
override _TARGETS += $(_V4P)
override _OBJS += $(_V4P_SRCS:%.c=$(_BUILD)/%.o)
override _CFLAGS += $(shell pkg-config --cflags libdrm)
_V4P_LDFLAGS = $(shell pkg-config --libs libdrm)
endif
# =====
all: $(_USTR) $(_DUMP)
all: $(_TARGETS)
install: all
mkdir -p $(DESTDIR)$(PREFIX)/bin
install -m755 $(_USTR) $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_USTR))
install -m755 $(_DUMP) $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_DUMP))
for i in $(subst .bin,,$(_TARGETS)); do \
install -m755 $$i.bin $(DESTDIR)$(PREFIX)/bin/$$i; \
done
install-strip: install
strip $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_USTR))
strip $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_DUMP))
for i in $(subst .bin,,$(_TARGETS)); do strip $(DESTDIR)$(PREFIX)/bin/$$i; done
$(_USTR): $(_USTR_SRCS:%.c=$(_BUILD)/%.o)
$(info == LD $@)
@ $(CC) $^ -o $@ $(_LDFLAGS) $(_USTR_LIBS)
$(ECHO) $(CC) $^ -o $@ $(_LDFLAGS) $(_USTR_LIBS)
$(_DUMP): $(_DUMP_SRCS:%.c=$(_BUILD)/%.o)
$(info == LD $@)
@ $(CC) $^ -o $@ $(_LDFLAGS) $(_DUMP_LIBS)
$(ECHO) $(CC) $^ -o $@ $(_LDFLAGS) $(_DUMP_LIBS)
$(_V4P): $(_V4P_SRCS:%.c=$(_BUILD)/%.o)
$(info == LD $@)
$(ECHO) $(CC) $^ -o $@ $(_LDFLAGS) $(_V4P_LDFLAGS) $(_V4P_LIBS)
$(_BUILD)/%.o: %.c
$(info -- CC $<)
@ mkdir -p $(dir $@) || true
@ $(CC) $< -o $@ $(_CFLAGS)
$(ECHO) mkdir -p $(dir $@) || true
$(ECHO) $(CC) $< -o $@ $(_CFLAGS)
clean:
rm -rf $(_USTR) $(_DUMP) $(_BUILD)
rm -rf $(_USTR) $(_DUMP) $(_V4P) $(_BUILD)
_OBJS = $(_USTR_SRCS:%.c=$(_BUILD)/%.o) $(_DUMP_SRCS:%.c=$(_BUILD)/%.o)
-include $(_OBJS:%.o=%.d)

View File

@@ -231,6 +231,8 @@ static int _dump_sink(
bool key_required,
_output_context_s *ctx) {
int retval = -1;
if (count == 0) {
count = -1;
}
@@ -300,18 +302,13 @@ static int _dump_sink(
}
}
int retval = 0;
goto ok;
retval = 0;
error:
retval = -1;
ok:
US_DELETE(sink, us_memsink_destroy);
us_frame_destroy(frame);
US_LOG_INFO("Bye-bye");
return retval;
error:
US_DELETE(sink, us_memsink_destroy);
us_frame_destroy(frame);
US_LOG_INFO("Bye-bye");
return retval;
}
static void _help(FILE *fp) {

View File

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

View File

@@ -22,13 +22,7 @@
#pragma once
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
#include "tools.h"
#include "types.h"
void us_base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *allocated);
void us_base64_encode(const u8 *data, uz size, char **encoded, uz *allocated);

View File

@@ -22,11 +22,14 @@
#pragma once
#include "types.h"
#define US_VERSION_MAJOR 5
#define US_VERSION_MINOR 40
#define US_VERSION_MINOR 57
#define US_MAKE_VERSION2(_major, _minor) #_major "." #_minor
#define US_MAKE_VERSION1(_major, _minor) US_MAKE_VERSION2(_major, _minor)
#define US_VERSION US_MAKE_VERSION1(US_VERSION_MAJOR, US_VERSION_MINOR)
#define US_VERSION_U ((unsigned)(US_VERSION_MAJOR * 1000 + US_VERSION_MINOR))
#define US_VERSION_U ((uint)(US_VERSION_MAJOR * 1000 + US_VERSION_MINOR))

1073
src/libs/device.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -22,46 +22,25 @@
#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/array.h"
#include "../libs/logging.h"
#include "../libs/threading.h"
#include "../libs/frame.h"
#include "../libs/xioctl.h"
#include "types.h"
#include "frame.h"
#define US_VIDEO_MIN_WIDTH ((unsigned)160)
#define US_VIDEO_MAX_WIDTH ((unsigned)10240)
#define US_VIDEO_MIN_WIDTH ((uint)160)
#define US_VIDEO_MAX_WIDTH ((uint)15360)
#define US_VIDEO_MIN_HEIGHT ((unsigned)120)
#define US_VIDEO_MAX_HEIGHT ((unsigned)4320)
#define US_VIDEO_MIN_HEIGHT ((uint)120)
#define US_VIDEO_MAX_HEIGHT ((uint)8640)
#define US_VIDEO_MAX_FPS ((unsigned)120)
#define US_VIDEO_MAX_FPS ((uint)120)
#define US_STANDARD_UNKNOWN V4L2_STD_UNKNOWN
#define US_STANDARDS_STR "PAL, NTSC, SECAM"
#define US_FORMAT_UNKNOWN -1
#define US_FORMATS_STR "YUYV, UYVY, RGB565, RGB24, MJPEG, JPEG"
#define US_FORMATS_STR "YUYV, YVYU, UYVY, RGB565, RGB24, BGR24, MJPEG, JPEG"
#define US_IO_METHOD_UNKNOWN -1
#define US_IO_METHODS_STR "MMAP, USERPTR"
@@ -75,18 +54,22 @@ typedef struct {
} us_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;
us_hw_buffer_s *hw_bufs;
bool capturing;
bool persistent_timeout_reported;
int fd;
uint width;
uint height;
uint format;
uint stride;
float hz;
uint hw_fps;
uint jpeg_quality;
uz raw_size;
uint n_bufs;
us_hw_buffer_s *hw_bufs;
bool dma;
enum v4l2_buf_type capture_type;
bool capture_mplane;
bool streamon;
bool persistent_timeout_reported;
} us_device_runtime_s;
typedef enum {
@@ -119,22 +102,22 @@ typedef struct {
typedef struct {
char *path;
unsigned input;
unsigned width;
unsigned height;
unsigned format;
unsigned jpeg_quality;
uint input;
uint width;
uint height;
uint format;
uint jpeg_quality;
v4l2_std_id standard;
enum v4l2_memory io_method;
bool dv_timings;
unsigned n_bufs;
unsigned desired_fps;
size_t min_frame_size;
uint n_bufs;
bool dma_export;
bool dma_required;
uint desired_fps;
uz min_frame_size;
bool persistent;
unsigned timeout;
uint timeout;
us_controls_s ctl;
us_device_runtime_s *run;
} us_device_s;
@@ -149,9 +132,7 @@ int us_device_parse_io_method(const char *str);
int us_device_open(us_device_s *dev);
void us_device_close(us_device_s *dev);
int us_device_export_to_dma(us_device_s *dev);
int us_device_switch_capturing(us_device_s *dev, bool enable);
int us_device_select(us_device_s *dev, bool *has_read, bool *has_write, bool *has_error);
int us_device_select(us_device_s *dev, bool *has_read, bool *has_error);
int us_device_grab_buffer(us_device_s *dev, us_hw_buffer_s **hw);
int us_device_release_buffer(us_device_s *dev, us_hw_buffer_s *hw);
int us_device_consume_event(us_device_s *dev);

View File

@@ -22,6 +22,16 @@
#include "frame.h"
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <linux/videodev2.h>
#include "types.h"
#include "tools.h"
us_frame_s *us_frame_init(void) {
us_frame_s *frame;
@@ -36,21 +46,21 @@ void us_frame_destroy(us_frame_s *frame) {
free(frame);
}
void us_frame_realloc_data(us_frame_s *frame, size_t size) {
void us_frame_realloc_data(us_frame_s *frame, uz size) {
if (frame->allocated < size) {
US_REALLOC(frame->data, size);
frame->allocated = size;
}
}
void us_frame_set_data(us_frame_s *frame, const uint8_t *data, size_t size) {
void us_frame_set_data(us_frame_s *frame, const u8 *data, uz size) {
us_frame_realloc_data(frame, size);
memcpy(frame->data, data, size);
frame->used = size;
}
void us_frame_append_data(us_frame_s *frame, const uint8_t *data, size_t size) {
const size_t new_used = frame->used + size;
void us_frame_append_data(us_frame_s *frame, const u8 *data, uz size) {
const uz new_used = frame->used + size;
us_frame_realloc_data(frame, new_used);
memcpy(frame->data + frame->used, data, size);
frame->used = new_used;
@@ -64,17 +74,19 @@ void us_frame_copy(const us_frame_s *src, us_frame_s *dest) {
bool us_frame_compare(const us_frame_s *a, const us_frame_s *b) {
return (
a->allocated && b->allocated
&& US_FRAME_COMPARE_META_USED_NOTS(a, b)
&& US_FRAME_COMPARE_GEOMETRY(a, b)
&& !memcmp(a->data, b->data, b->used)
);
}
unsigned us_frame_get_padding(const us_frame_s *frame) {
unsigned bytes_per_pixel = 0;
uint us_frame_get_padding(const us_frame_s *frame) {
uint bytes_per_pixel = 0;
switch (frame->format) {
case V4L2_PIX_FMT_YUYV:
case V4L2_PIX_FMT_YVYU:
case V4L2_PIX_FMT_UYVY:
case V4L2_PIX_FMT_RGB565: bytes_per_pixel = 2; break;
case V4L2_PIX_FMT_BGR24:
case V4L2_PIX_FMT_RGB24: bytes_per_pixel = 3; break;
// case V4L2_PIX_FMT_H264:
case V4L2_PIX_FMT_MJPEG:
@@ -87,13 +99,17 @@ unsigned us_frame_get_padding(const us_frame_s *frame) {
return 0;
}
const char *us_fourcc_to_string(unsigned format, char *buf, size_t size) {
bool us_is_jpeg(uint format) {
return (format == V4L2_PIX_FMT_JPEG || format == V4L2_PIX_FMT_MJPEG);
}
const char *us_fourcc_to_string(uint format, char *buf, uz size) {
assert(size >= 8);
buf[0] = format & 0x7F;
buf[1] = (format >> 8) & 0x7F;
buf[2] = (format >> 16) & 0x7F;
buf[3] = (format >> 24) & 0x7F;
if (format & ((unsigned)1 << 31)) {
if (format & ((uint)1 << 31)) {
buf[4] = '-';
buf[5] = 'B';
buf[6] = 'E';

View File

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

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

@@ -0,0 +1,158 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "frametext.h"
#include <string.h>
#include <sys/types.h>
#include <linux/videodev2.h>
#include "tools.h"
#include "frame.h"
#include "frametext_font.h"
static void _frametext_draw_line(
us_frametext_s *ft, const char *line,
uint scale_x, uint scale_y,
uint start_x, uint start_y);
us_frametext_s *us_frametext_init(void) {
us_frametext_s *ft;
US_CALLOC(ft, 1);
ft->frame = us_frame_init();
return ft;
}
void us_frametext_destroy(us_frametext_s *ft) {
us_frame_destroy(ft->frame);
US_DELETE(ft->text, free);
free(ft);
}
void us_frametext_draw(us_frametext_s *ft, const char *text, uint width, uint height) {
assert(width > 0);
assert(height > 0);
us_frame_s *const frame = ft->frame;
if (
frame->width == width && frame->height == height
&& ft->text != NULL && !strcmp(ft->text, text)
) {
return;
}
US_DELETE(ft->text, free);
ft->text = us_strdup(text);
strcpy(ft->text, text);
frame->width = width;
frame->height = height;
frame->format = V4L2_PIX_FMT_RGB24;
frame->stride = width * 3;
frame->used = width * height * 3;
us_frame_realloc_data(frame, frame->used);
memset(frame->data, 0, frame->used);
if (frame->width == 0 || frame->height == 0) {
return;
}
char *str = us_strdup(text);
char *line;
char *rest;
uint block_width = 0;
uint block_height = 0;
while ((line = strtok_r((block_height == 0 ? str : NULL), "\n", &rest)) != NULL) {
block_width = US_MAX(strlen(line) * 8, block_width);
block_height += 8;
}
if (block_width == 0 || block_height == 0) {
goto empty;
}
uint scale_x = frame->width / block_width / 2;
uint scale_y = frame->height / block_height / 3;
if (scale_x < scale_y / 1.5) {
scale_y = scale_x * 1.5;
} else if (scale_y < scale_x * 1.5) {
scale_x = scale_y / 1.5;
}
strcpy(str, text);
const uint start_y = (frame->height >= (block_height * scale_y)
? ((frame->height - (block_height * scale_y)) / 2)
: 0);
uint n_line = 0;
while ((line = strtok_r((n_line == 0 ? str : NULL), "\n", &rest)) != NULL) {
const uint line_width = strlen(line) * 8 * scale_x;
const uint start_x = (frame->width >= line_width
? ((frame->width - line_width) / 2)
: 0);
_frametext_draw_line(ft, line, scale_x, scale_y, start_x, start_y + n_line * 8 * scale_y);
++n_line;
}
empty:
free(str);
}
void _frametext_draw_line(
us_frametext_s *ft, const char *line,
uint scale_x, uint scale_y,
uint start_x, uint start_y) {
us_frame_s *const frame = ft->frame;
const size_t len = strlen(line);
for (uint ch_y = 0; ch_y < 8 * scale_y; ++ch_y) {
const uint canvas_y = start_y + ch_y;
for (uint ch_x = 0; ch_x < 8 * len * scale_x; ++ch_x) {
if ((start_x + ch_x) >= frame->width) {
break;
}
const uint canvas_x = (start_x + ch_x) * 3;
const uint offset = canvas_y * frame->stride + canvas_x;
if (offset >= frame->used) {
break;
}
const u8 ch = US_MIN((u8)line[ch_x / 8 / scale_x], sizeof(US_FRAMETEXT_FONT) / 8 - 1);
const uint ch_byte = (ch_y / scale_y) % 8;
const uint ch_bit = (ch_x / scale_x) % 8;
const bool pix_on = !!(US_FRAMETEXT_FONT[ch][ch_byte] & (1 << ch_bit));
u8 *const r = &frame->data[offset];
u8 *const g = r + 1;
u8 *const b = r + 2;
*r = pix_on * 0x65; // RGB/BGR-friendly
*g = pix_on * 0x65;
*b = pix_on * 0x65;
}
}
}

39
src/libs/frametext.h Normal file
View File

@@ -0,0 +1,39 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include "types.h"
#include "frame.h"
typedef struct {
char *text;
us_frame_s *frame;
} us_frametext_s;
us_frametext_s *us_frametext_init(void);
void us_frametext_destroy(us_frametext_s *ft);
void us_frametext_draw(us_frametext_s *ft, const char *text, uint width, uint height);

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

@@ -0,0 +1,158 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "frametext_font.h"
#include "types.h"
const u8 US_FRAMETEXT_FONT[128][8] = {
// https://github.com/dhepper/font8x8/blob/master/font8x8_basic.h
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0000 (nul)
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0001
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0002
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0003
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0004
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0005
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0006
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0007
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0008
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0009
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000A
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000B
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000C
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000D
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000E
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000F
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0010
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0011
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0012
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0013
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0014
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0015
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0016
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0017
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0018
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0019
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001A
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001B
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001C
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001D
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001E
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001F
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0020 (space)
{0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00}, // U+0021 (!)
{0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0022 (")
{0x36, 0x36, 0x7F, 0x36, 0x7F, 0x36, 0x36, 0x00}, // U+0023 (#)
{0x0C, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x0C, 0x00}, // U+0024 ($)
{0x00, 0x63, 0x33, 0x18, 0x0C, 0x66, 0x63, 0x00}, // U+0025 (%)
{0x1C, 0x36, 0x1C, 0x6E, 0x3B, 0x33, 0x6E, 0x00}, // U+0026 (&)
{0x06, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0027 (')
{0x18, 0x0C, 0x06, 0x06, 0x06, 0x0C, 0x18, 0x00}, // U+0028 (()
{0x06, 0x0C, 0x18, 0x18, 0x18, 0x0C, 0x06, 0x00}, // U+0029 ())
{0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00}, // U+002A (*)
{0x00, 0x0C, 0x0C, 0x3F, 0x0C, 0x0C, 0x00, 0x00}, // U+002B (+)
{0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // U+002C (,)
{0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00}, // U+002D (-)
{0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // U+002E (.)
{0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00}, // U+002F (/)
{0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00}, // U+0030 (0)
{0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00}, // U+0031 (1)
{0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00}, // U+0032 (2)
{0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00}, // U+0033 (3)
{0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00}, // U+0034 (4)
{0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00}, // U+0035 (5)
{0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00}, // U+0036 (6)
{0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00}, // U+0037 (7)
{0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00}, // U+0038 (8)
{0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00}, // U+0039 (9)
{0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // U+003A (:)
{0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // U+003B (;)
{0x18, 0x0C, 0x06, 0x03, 0x06, 0x0C, 0x18, 0x00}, // U+003C (<)
{0x00, 0x00, 0x3F, 0x00, 0x00, 0x3F, 0x00, 0x00}, // U+003D (=)
{0x06, 0x0C, 0x18, 0x30, 0x18, 0x0C, 0x06, 0x00}, // U+003E (>)
{0x1E, 0x33, 0x30, 0x18, 0x0C, 0x00, 0x0C, 0x00}, // U+003F (?)
{0x3E, 0x63, 0x7B, 0x7B, 0x7B, 0x03, 0x1E, 0x00}, // U+0040 (@)
{0x0C, 0x1E, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x00}, // U+0041 (A)
{0x3F, 0x66, 0x66, 0x3E, 0x66, 0x66, 0x3F, 0x00}, // U+0042 (B)
{0x3C, 0x66, 0x03, 0x03, 0x03, 0x66, 0x3C, 0x00}, // U+0043 (C)
{0x1F, 0x36, 0x66, 0x66, 0x66, 0x36, 0x1F, 0x00}, // U+0044 (D)
{0x7F, 0x46, 0x16, 0x1E, 0x16, 0x46, 0x7F, 0x00}, // U+0045 (E)
{0x7F, 0x46, 0x16, 0x1E, 0x16, 0x06, 0x0F, 0x00}, // U+0046 (F)
{0x3C, 0x66, 0x03, 0x03, 0x73, 0x66, 0x7C, 0x00}, // U+0047 (G)
{0x33, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x33, 0x00}, // U+0048 (H)
{0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0049 (I)
{0x78, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E, 0x00}, // U+004A (J)
{0x67, 0x66, 0x36, 0x1E, 0x36, 0x66, 0x67, 0x00}, // U+004B (K)
{0x0F, 0x06, 0x06, 0x06, 0x46, 0x66, 0x7F, 0x00}, // U+004C (L)
{0x63, 0x77, 0x7F, 0x7F, 0x6B, 0x63, 0x63, 0x00}, // U+004D (M)
{0x63, 0x67, 0x6F, 0x7B, 0x73, 0x63, 0x63, 0x00}, // U+004E (N)
{0x1C, 0x36, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x00}, // U+004F (O)
{0x3F, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x0F, 0x00}, // U+0050 (P)
{0x1E, 0x33, 0x33, 0x33, 0x3B, 0x1E, 0x38, 0x00}, // U+0051 (Q)
{0x3F, 0x66, 0x66, 0x3E, 0x36, 0x66, 0x67, 0x00}, // U+0052 (R)
{0x1E, 0x33, 0x07, 0x0E, 0x38, 0x33, 0x1E, 0x00}, // U+0053 (S)
{0x3F, 0x2D, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0054 (T)
{0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0x00}, // U+0055 (U)
{0x33, 0x33, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // U+0056 (V)
{0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00}, // U+0057 (W)
{0x63, 0x63, 0x36, 0x1C, 0x1C, 0x36, 0x63, 0x00}, // U+0058 (X)
{0x33, 0x33, 0x33, 0x1E, 0x0C, 0x0C, 0x1E, 0x00}, // U+0059 (Y)
{0x7F, 0x63, 0x31, 0x18, 0x4C, 0x66, 0x7F, 0x00}, // U+005A (Z)
{0x1E, 0x06, 0x06, 0x06, 0x06, 0x06, 0x1E, 0x00}, // U+005B ([)
{0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00}, // U+005C (\)
{0x1E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1E, 0x00}, // U+005D (])
{0x08, 0x1C, 0x36, 0x63, 0x00, 0x00, 0x00, 0x00}, // U+005E (^)
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF}, // U+005F (_)
{0x0C, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0060 (`)
{0x00, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x6E, 0x00}, // U+0061 (a)
{0x07, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3B, 0x00}, // U+0062 (b)
{0x00, 0x00, 0x1E, 0x33, 0x03, 0x33, 0x1E, 0x00}, // U+0063 (c)
{0x38, 0x30, 0x30, 0x3e, 0x33, 0x33, 0x6E, 0x00}, // U+0064 (d)
{0x00, 0x00, 0x1E, 0x33, 0x3f, 0x03, 0x1E, 0x00}, // U+0065 (e)
{0x1C, 0x36, 0x06, 0x0f, 0x06, 0x06, 0x0F, 0x00}, // U+0066 (f)
{0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+0067 (g)
{0x07, 0x06, 0x36, 0x6E, 0x66, 0x66, 0x67, 0x00}, // U+0068 (h)
{0x0C, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0069 (i)
{0x30, 0x00, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E}, // U+006A (j)
{0x07, 0x06, 0x66, 0x36, 0x1E, 0x36, 0x67, 0x00}, // U+006B (k)
{0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+006C (l)
{0x00, 0x00, 0x33, 0x7F, 0x7F, 0x6B, 0x63, 0x00}, // U+006D (m)
{0x00, 0x00, 0x1F, 0x33, 0x33, 0x33, 0x33, 0x00}, // U+006E (n)
{0x00, 0x00, 0x1E, 0x33, 0x33, 0x33, 0x1E, 0x00}, // U+006F (o)
{0x00, 0x00, 0x3B, 0x66, 0x66, 0x3E, 0x06, 0x0F}, // U+0070 (p)
{0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x78}, // U+0071 (q)
{0x00, 0x00, 0x3B, 0x6E, 0x66, 0x06, 0x0F, 0x00}, // U+0072 (r)
{0x00, 0x00, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x00}, // U+0073 (s)
{0x08, 0x0C, 0x3E, 0x0C, 0x0C, 0x2C, 0x18, 0x00}, // U+0074 (t)
{0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x6E, 0x00}, // U+0075 (u)
{0x00, 0x00, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // U+0076 (v)
{0x00, 0x00, 0x63, 0x6B, 0x7F, 0x7F, 0x36, 0x00}, // U+0077 (w)
{0x00, 0x00, 0x63, 0x36, 0x1C, 0x36, 0x63, 0x00}, // U+0078 (x)
{0x00, 0x00, 0x33, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+0079 (y)
{0x00, 0x00, 0x3F, 0x19, 0x0C, 0x26, 0x3F, 0x00}, // U+007A (z)
{0x38, 0x0C, 0x0C, 0x07, 0x0C, 0x0C, 0x38, 0x00}, // U+007B ({)
{0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00}, // U+007C (|)
{0x07, 0x0C, 0x0C, 0x38, 0x0C, 0x0C, 0x07, 0x00}, // U+007D (})
{0x6E, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+007E (~)
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+007F
};

View File

@@ -22,13 +22,7 @@
#pragma once
#include <stdint.h>
#include <sys/types.h>
#include "types.h"
extern const unsigned US_BLANK_JPEG_WIDTH;
extern const unsigned US_BLANK_JPEG_HEIGHT;
extern const size_t US_BLANK_JPEG_DATA_SIZE;
extern const uint8_t US_BLANK_JPEG_DATA[];
extern const u8 US_FRAMETEXT_FONT[128][8];

View File

@@ -22,6 +22,10 @@
#include "logging.h"
#include <stdbool.h>
#include <pthread.h>
enum us_log_level_t us_g_log_level;

View File

@@ -23,7 +23,6 @@
#pragma once
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
@@ -33,6 +32,7 @@
#include <pthread.h>
#include "types.h"
#include "tools.h"
#include "threading.h"
@@ -91,7 +91,7 @@ extern pthread_mutex_t us_g_log_mutex;
#define US_LOG_PRINTF_NOLOCK(x_label_color, x_label, x_msg_color, x_msg, ...) { \
char m_tname_buf[US_MAX_THREAD_NAME] = {0}; \
char m_tname_buf[US_THREAD_NAME_SIZE] = {0}; \
us_thread_get_name(m_tname_buf); \
if (us_g_log_colored) { \
fprintf(stderr, US_COLOR_GRAY "-- " x_label_color x_label US_COLOR_GRAY \

View File

@@ -22,10 +22,26 @@
#include "memsink.h"
#include <stdatomic.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <assert.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include "types.h"
#include "tools.h"
#include "logging.h"
#include "frame.h"
#include "memsinksh.h"
us_memsink_s *us_memsink_init(
const char *name, const char *obj, bool server,
mode_t mode, bool rm, unsigned client_ttl, unsigned timeout) {
mode_t mode, bool rm, uint client_ttl, uint timeout) {
us_memsink_s *sink;
US_CALLOC(sink, 1);
@@ -58,12 +74,11 @@ us_memsink_s *us_memsink_init(
US_LOG_PERROR("%s-sink: Can't mmap shared memory", name);
goto error;
}
return sink;
error:
us_memsink_destroy(sink);
return NULL;
error:
us_memsink_destroy(sink);
return NULL;
}
void us_memsink_destroy(us_memsink_s *sink) {
@@ -114,10 +129,10 @@ bool us_memsink_server_check(us_memsink_s *sink, const us_frame_s *frame) {
US_LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
return false;
}
return (has_clients || !US_FRAME_COMPARE_META_USED_NOTS(sink->mem, frame));;
return (has_clients || !US_FRAME_COMPARE_GEOMETRY(sink->mem, frame));;
}
int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *const key_requested) {
int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *key_requested) {
assert(sink->server);
const long double now = us_get_now_monotonic();
@@ -164,7 +179,7 @@ int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *con
return 0;
}
int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool *const key_requested, bool key_required) { // cppcheck-suppress unusedFunction
int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool *key_requested, bool key_required) {
assert(!sink->server); // Client only
if (us_flock_timedwait_monotonic(sink->fd, sink->timeout) < 0) {
@@ -176,6 +191,7 @@ int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool *const key
}
int retval = -2; // Not updated
if (sink->mem->magic == US_MEMSINK_MAGIC) {
if (sink->mem->version != US_MEMSINK_VERSION) {
US_LOG_ERROR("%s-sink: Protocol version mismatch: sink=%u, required=%u",
@@ -196,10 +212,10 @@ int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool *const key
}
}
done:
if (flock(sink->fd, LOCK_UN) < 0) {
US_LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
return -1;
}
return retval;
done:
if (flock(sink->fd, LOCK_UN) < 0) {
US_LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
retval = -1;
}
return retval;
}

View File

@@ -22,47 +22,37 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <stdatomic.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include "tools.h"
#include "logging.h"
#include "types.h"
#include "frame.h"
#include "memsinksh.h"
typedef struct {
const char *name;
const char *obj;
bool server;
bool rm;
unsigned client_ttl; // Only for server
unsigned timeout;
const char *name;
const char *obj;
bool server;
bool rm;
uint client_ttl; // Only for server
uint timeout;
int fd;
us_memsink_shared_s *mem;
uint64_t last_id;
u64 last_id;
atomic_bool has_clients; // Only for server
} us_memsink_s;
us_memsink_s *us_memsink_init(
const char *name, const char *obj, bool server,
mode_t mode, bool rm, unsigned client_ttl, unsigned timeout);
mode_t mode, bool rm, uint client_ttl, uint timeout);
void us_memsink_destroy(us_memsink_s *sink);
bool us_memsink_server_check(us_memsink_s *sink, const us_frame_s *frame);
int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *const key_requested);
int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *key_requested);
int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool *const key_requested, bool key_required);
int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool *key_requested, bool key_required);

View File

@@ -22,45 +22,43 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/mman.h>
#include "types.h"
#define US_MEMSINK_MAGIC ((uint64_t)0xCAFEBABECAFEBABE)
#define US_MEMSINK_VERSION ((uint32_t)4)
#define US_MEMSINK_MAGIC ((u64)0xCAFEBABECAFEBABE)
#define US_MEMSINK_VERSION ((u32)4)
#ifndef US_CFG_MEMSINK_MAX_DATA
# define US_CFG_MEMSINK_MAX_DATA 33554432
#endif
#define US_MEMSINK_MAX_DATA ((size_t)(US_CFG_MEMSINK_MAX_DATA))
#define US_MEMSINK_MAX_DATA ((uz)(US_CFG_MEMSINK_MAX_DATA))
typedef struct {
uint64_t magic;
uint32_t version;
u64 magic;
u32 version;
uint64_t id;
u64 id;
size_t used;
unsigned width;
unsigned height;
unsigned format;
unsigned stride;
bool online;
bool key;
unsigned gop;
uz used;
uint width;
uint height;
uint format;
uint stride;
bool online;
bool key;
uint gop;
long double grab_ts;
long double encode_begin_ts;
long double encode_end_ts;
ldf grab_ts;
ldf encode_begin_ts;
ldf encode_end_ts;
long double last_client_ts;
bool key_requested;
ldf last_client_ts;
bool key_requested;
uint8_t data[US_MEMSINK_MAX_DATA];
u8 data[US_MEMSINK_MAX_DATA];
} us_memsink_shared_s;

View File

@@ -22,10 +22,17 @@
#include "options.h"
#include <string.h>
#include <ctype.h>
#include <getopt.h>
#include <assert.h>
void us_build_short_options(const struct option opts[], char *short_opts, size_t size) {
#include "types.h"
void us_build_short_options(const struct option opts[], char *short_opts, uz size) {
memset(short_opts, 0, size);
for (unsigned short_index = 0, opt_index = 0; opts[opt_index].name != NULL; ++opt_index) {
for (uint 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;

View File

@@ -22,12 +22,9 @@
#pragma once
#include <string.h>
#include <ctype.h>
#include <getopt.h>
#include <assert.h>
#include <sys/types.h>
#include "types.h"
void us_build_short_options(const struct option opts[], char *short_opts, size_t size);
void us_build_short_options(const struct option opts[], char *short_opts, uz size);

View File

@@ -25,8 +25,6 @@
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#if defined(__linux__)
# define HAS_PDEATHSIG
@@ -58,6 +56,7 @@
# include <sys/procctl.h>
# endif
#endif
#include "types.h"
#ifdef WITH_SETPROCTITLE
# include "tools.h"
#endif
@@ -98,18 +97,18 @@ INLINE int us_process_track_parent_death(void) {
#ifdef WITH_SETPROCTITLE
# pragma GCC diagnostic ignored "-Wunused-parameter"
# pragma GCC diagnostic push
INLINE void us_process_set_name_prefix(int argc, char *argv[], const char *prefix) {
INLINE void us_process_set_name_prefix(int argc, char *argv[], const char *prefix) { // cppcheck-suppress constParameter
# pragma GCC diagnostic pop
char *cmdline = NULL;
size_t allocated = 2048;
size_t used = 0;
uz allocated = 2048;
uz used = 0;
US_REALLOC(cmdline, allocated);
cmdline[0] = '\0';
for (int index = 0; index < argc; ++index) {
size_t arg_len = strlen(argv[index]);
uz arg_len = strlen(argv[index]);
if (used + arg_len + 16 >= allocated) {
allocated += arg_len + 2048;
US_REALLOC(cmdline, allocated); // cppcheck-suppress memleakOnRealloc // False-positive (ok with assert)

View File

@@ -22,8 +22,18 @@
#include "queue.h"
#include <errno.h>
#include <time.h>
#include <assert.h>
us_queue_s *us_queue_init(unsigned capacity) {
#include <pthread.h>
#include "types.h"
#include "tools.h"
#include "threading.h"
us_queue_s *us_queue_init(uint capacity) {
us_queue_s *queue;
US_CALLOC(queue, 1);
US_CALLOC(queue->items, capacity);
@@ -61,7 +71,7 @@ void us_queue_destroy(us_queue_s *queue) {
} \
}
int us_queue_put(us_queue_s *queue, void *item, long double timeout) {
int us_queue_put(us_queue_s *queue, void *item, ldf timeout) {
US_MUTEX_LOCK(queue->mutex);
if (timeout == 0) {
if (queue->size == queue->capacity) {
@@ -80,7 +90,7 @@ int us_queue_put(us_queue_s *queue, void *item, long double timeout) {
return 0;
}
int us_queue_get(us_queue_s *queue, void **item, long double timeout) {
int us_queue_get(us_queue_s *queue, void **item, ldf timeout) {
US_MUTEX_LOCK(queue->mutex);
_WAIT_OR_UNLOCK(queue->size == 0, queue->empty_cond);
*item = queue->items[queue->out];
@@ -94,9 +104,9 @@ int us_queue_get(us_queue_s *queue, void **item, long double timeout) {
#undef _WAIT_OR_UNLOCK
int us_queue_get_free(us_queue_s *queue) {
/*int us_queue_get_free(us_queue_s *queue) {
US_MUTEX_LOCK(queue->mutex);
const unsigned size = queue->size;
const uint size = queue->size;
US_MUTEX_UNLOCK(queue->mutex);
return queue->capacity - size;
}
}*/

View File

@@ -22,24 +22,20 @@
#pragma once
#include <errno.h>
#include <time.h>
#include <assert.h>
#include <pthread.h>
#include "uslibs/tools.h"
#include "uslibs/threading.h"
#include "types.h"
#include "tools.h"
// Based on https://github.com/seifzadeh/c-pthread-queue/blob/master/queue.h
typedef struct {
void **items;
unsigned size;
unsigned capacity;
unsigned in;
unsigned out;
uint size;
uint capacity;
uint in;
uint out;
pthread_mutex_t mutex;
pthread_cond_t full_cond;
@@ -60,9 +56,9 @@ typedef struct {
}
us_queue_s *us_queue_init(unsigned capacity);
us_queue_s *us_queue_init(uint capacity);
void us_queue_destroy(us_queue_s *queue);
int us_queue_put(us_queue_s *queue, void *item, long double timeout);
int us_queue_get(us_queue_s *queue, void **item, long double timeout);
int us_queue_get_free(us_queue_s *queue);
int us_queue_put(us_queue_s *queue, void *item, ldf timeout);
int us_queue_get(us_queue_s *queue, void **item, ldf timeout);
// int us_queue_get_free(us_queue_s *queue);

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

@@ -0,0 +1,86 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include <assert.h>
#include "ring.h"
#include "types.h"
#include "tools.h"
#include "queue.h"
int _acquire(us_ring_s *ring, us_queue_s *queue, ldf timeout);
void _release(us_ring_s *ring, us_queue_s *queue, uint index);
us_ring_s *us_ring_init(uint capacity) {
us_ring_s *ring;
US_CALLOC(ring, 1);
US_CALLOC(ring->items, capacity);
US_CALLOC(ring->places, capacity);
ring->capacity = capacity;
ring->producer = us_queue_init(capacity);
ring->consumer = us_queue_init(capacity);
for (uint index = 0; index < capacity; ++index) {
ring->places[index] = index; // XXX: Just to avoid casting between pointer and uint
assert(!us_queue_put(ring->producer, (void*)(ring->places + index), 0));
}
return ring;
}
void us_ring_destroy(us_ring_s *ring) {
us_queue_destroy(ring->consumer);
us_queue_destroy(ring->producer);
free(ring->places);
free(ring->items);
free(ring);
}
int us_ring_producer_acquire(us_ring_s *ring, ldf timeout) {
return _acquire(ring, ring->producer, timeout);
}
void us_ring_producer_release(us_ring_s *ring, uint index) {
_release(ring, ring->consumer, index);
}
int us_ring_consumer_acquire(us_ring_s *ring, ldf timeout) {
return _acquire(ring, ring->consumer, timeout);
}
void us_ring_consumer_release(us_ring_s *ring, uint index) {
_release(ring, ring->producer, index);
}
int _acquire(us_ring_s *ring, us_queue_s *queue, ldf timeout) {
(void)ring;
uint *place;
if (us_queue_get(queue, (void**)&place, timeout) < 0) {
return -1;
}
return *place;
}
void _release(us_ring_s *ring, us_queue_s *queue, uint index) {
assert(!us_queue_put(queue, (void*)(ring->places + index), 0));
}

63
src/libs/ring.h Normal file
View File

@@ -0,0 +1,63 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include "types.h"
#include "queue.h"
typedef struct {
uz capacity;
void **items;
uint *places;
us_queue_s *producer;
us_queue_s *consumer;
} us_ring_s;
#define US_RING_INIT_WITH_ITEMS(x_ring, x_capacity, x_init_item) { \
(x_ring) = us_ring_init(x_capacity); \
for (uz m_index = 0; m_index < (x_ring)->capacity; ++m_index) { \
(x_ring)->items[m_index] = x_init_item(); \
} \
}
#define US_RING_DELETE_WITH_ITEMS(x_ring, x_destroy_item) { \
if (x_ring) { \
for (uz m_index = 0; m_index < (x_ring)->capacity; ++m_index) { \
x_destroy_item((x_ring)->items[m_index]); \
} \
us_ring_destroy(x_ring); \
} \
}
us_ring_s *us_ring_init(uint capacity);
void us_ring_destroy(us_ring_s *ring);
int us_ring_producer_acquire(us_ring_s *ring, ldf timeout);
void us_ring_producer_release(us_ring_s *ring, uint index);
int us_ring_consumer_acquire(us_ring_s *ring, ldf timeout);
void us_ring_consumer_release(us_ring_s *ring, uint index);

View File

@@ -26,7 +26,6 @@
#include <unistd.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <pthread.h>
@@ -37,13 +36,14 @@
# endif
#endif
#include "types.h"
#include "tools.h"
#ifdef PTHREAD_MAX_NAMELEN_NP
# define US_MAX_THREAD_NAME ((size_t)(PTHREAD_MAX_NAMELEN_NP))
# define US_THREAD_NAME_SIZE ((uz)(PTHREAD_MAX_NAMELEN_NP))
#else
# define US_MAX_THREAD_NAME ((size_t)16)
# define US_THREAD_NAME_SIZE ((uz)16)
#endif
#define US_THREAD_CREATE(x_tid, x_func, x_arg) assert(!pthread_create(&(x_tid), NULL, (x_func), (x_arg)))
@@ -51,8 +51,8 @@
#ifdef WITH_PTHREAD_NP
# define US_THREAD_RENAME(x_fmt, ...) { \
char m_new_tname_buf[US_MAX_THREAD_NAME] = {0}; \
assert(snprintf(m_new_tname_buf, US_MAX_THREAD_NAME, (x_fmt), ##__VA_ARGS__) > 0); \
char m_new_tname_buf[US_THREAD_NAME_SIZE] = {0}; \
US_SNPRINTF(m_new_tname_buf, (US_THREAD_NAME_SIZE - 1), (x_fmt), ##__VA_ARGS__); \
us_thread_set_name(m_new_tname_buf); \
}
#else
@@ -89,12 +89,12 @@ INLINE void us_thread_get_name(char *name) { // Always required for logging
#ifdef WITH_PTHREAD_NP
int retval = -1;
# if defined(__linux__) || defined (__NetBSD__)
retval = pthread_getname_np(pthread_self(), name, US_MAX_THREAD_NAME);
retval = pthread_getname_np(pthread_self(), name, US_THREAD_NAME_SIZE - 1);
# elif \
(defined(__FreeBSD__) && defined(__FreeBSD_version) && __FreeBSD_version >= 1103500) \
|| (defined(__OpenBSD__) && defined(OpenBSD) && OpenBSD >= 201905) \
|| defined(__DragonFly__)
pthread_get_name_np(pthread_self(), name, US_MAX_THREAD_NAME);
pthread_get_name_np(pthread_self(), name, US_THREAD_NAME_SIZE - 1);
if (name[0] != '\0') {
retval = 0;
}
@@ -118,7 +118,7 @@ INLINE void us_thread_get_name(char *name) { // Always required for logging
const pid_t tid = 0; // Makes cppcheck happy
# warning gettid() not implemented
#endif
assert(snprintf(name, US_MAX_THREAD_NAME, "tid=%d", tid) > 0);
US_SNPRINTF(name, (US_THREAD_NAME_SIZE - 1), "tid=%d", tid);
#ifdef WITH_PTHREAD_NP
}

View File

@@ -23,19 +23,15 @@
#pragma once
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <locale.h>
#include <errno.h>
#include <math.h>
#include <time.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/file.h>
#if defined(__GLIBC__) && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 32
@@ -44,6 +40,8 @@
# include <signal.h>
#endif
#include "types.h"
#ifdef NDEBUG
# error WTF dude? Asserts are good things!
@@ -57,14 +55,27 @@
#define RN "\r\n"
#define INLINE inline __attribute__((always_inline))
#define UNUSED __attribute__((unused))
#define US_CALLOC(x_dest, x_nmemb) assert(((x_dest) = calloc((x_nmemb), sizeof(*(x_dest)))) != NULL)
#define US_REALLOC(x_dest, x_nmemb) assert(((x_dest) = realloc((x_dest), (x_nmemb) * sizeof(*(x_dest)))) != NULL)
#define US_DELETE(x_dest, x_free) { if (x_dest) { x_free(x_dest); } }
#define US_DELETE(x_dest, x_free) { if (x_dest) { x_free(x_dest); x_dest = NULL; } }
#define US_CLOSE_FD(x_dest) { if (x_dest >= 0) { close(x_dest); x_dest = -1; } }
#define US_MEMSET_ZERO(x_obj) memset(&(x_obj), 0, sizeof(x_obj))
#define US_ASPRINTF(x_dest, x_fmt, ...) assert(asprintf(&(x_dest), (x_fmt), ##__VA_ARGS__) >= 0)
#define US_SNPRINTF(x_dest, x_size, x_fmt, ...) assert(snprintf((x_dest), (x_size), (x_fmt), ##__VA_ARGS__) > 0)
#define US_ASPRINTF(x_dest, x_fmt, ...) assert(asprintf(&(x_dest), (x_fmt), ##__VA_ARGS__) > 0)
#define US_MIN(x_a, x_b) ({ \
__typeof__(x_a) m_a = (x_a); \
__typeof__(x_b) m_b = (x_b); \
(m_a < m_b ? m_a : m_b); \
})
#define US_MAX(x_a, x_b) ({ \
__typeof__(x_a) m_a = (x_a); \
__typeof__(x_b) m_b = (x_b); \
(m_a > m_b ? m_a : m_b); \
})
INLINE char *us_strdup(const char *str) {
@@ -77,23 +88,15 @@ INLINE const char *us_bool_to_string(bool flag) {
return (flag ? "true" : "false");
}
INLINE size_t us_align_size(size_t size, size_t to) {
INLINE uz us_align_size(uz size, uz to) {
return ((size + (to - 1)) & ~(to - 1));
}
INLINE unsigned us_min_u(unsigned a, unsigned b) {
return (a < b ? a : b);
INLINE sll us_floor_ms(ldf now) {
return (sll)now - (now < (sll)now); // floor()
}
INLINE unsigned us_max_u(unsigned a, unsigned b) {
return (a > b ? a : b);
}
INLINE long long us_floor_ms(long double now) {
return (long long)now - (now < (long long)now); // floor()
}
INLINE uint32_t us_triple_u32(uint32_t x) {
INLINE u32 us_triple_u32(u32 x) {
// https://nullprogram.com/blog/2018/07/31/
x ^= x >> 17;
x *= UINT32_C(0xED5AD4BB);
@@ -117,48 +120,38 @@ INLINE void us_get_now(clockid_t clk_id, time_t *sec, long *msec) {
}
}
#if defined(CLOCK_MONOTONIC_RAW)
# define _X_CLOCK_MONOTONIC CLOCK_MONOTONIC_RAW
#elif defined(CLOCK_MONOTONIC_FAST)
# define _X_CLOCK_MONOTONIC CLOCK_MONOTONIC_FAST
#else
# define _X_CLOCK_MONOTONIC CLOCK_MONOTONIC
#endif
INLINE long double us_get_now_monotonic(void) {
INLINE ldf us_get_now_monotonic(void) {
time_t sec;
long msec;
us_get_now(_X_CLOCK_MONOTONIC, &sec, &msec);
return (long double)sec + ((long double)msec) / 1000;
us_get_now(CLOCK_MONOTONIC, &sec, &msec);
return (ldf)sec + ((ldf)msec) / 1000;
}
INLINE uint64_t us_get_now_monotonic_u64(void) {
INLINE u64 us_get_now_monotonic_u64(void) {
struct timespec ts;
assert(!clock_gettime(_X_CLOCK_MONOTONIC, &ts));
return (uint64_t)(ts.tv_nsec / 1000) + (uint64_t)ts.tv_sec * 1000000;
assert(!clock_gettime(CLOCK_MONOTONIC, &ts));
return (u64)(ts.tv_nsec / 1000) + (u64)ts.tv_sec * 1000000;
}
#undef _X_CLOCK_MONOTONIC
INLINE uint64_t us_get_now_id(void) {
const uint64_t now = us_get_now_monotonic_u64();
return (uint64_t)us_triple_u32(now) | ((uint64_t)us_triple_u32(now + 12345) << 32);
INLINE u64 us_get_now_id(void) {
const u64 now = us_get_now_monotonic_u64();
return (u64)us_triple_u32(now) | ((u64)us_triple_u32(now + 12345) << 32);
}
INLINE long double us_get_now_real(void) {
INLINE ldf us_get_now_real(void) {
time_t sec;
long msec;
us_get_now(CLOCK_REALTIME, &sec, &msec);
return (long double)sec + ((long double)msec) / 1000;
return (ldf)sec + ((ldf)msec) / 1000;
}
INLINE unsigned us_get_cores_available(void) {
INLINE uint us_get_cores_available(void) {
long cores_sysconf = sysconf(_SC_NPROCESSORS_ONLN);
cores_sysconf = (cores_sysconf < 0 ? 0 : cores_sysconf);
return us_max_u(us_min_u(cores_sysconf, 4), 1);
return US_MAX(US_MIN(cores_sysconf, 4), 1);
}
INLINE void us_ld_to_timespec(long double ld, struct timespec *ts) {
INLINE void us_ld_to_timespec(ldf ld, struct timespec *ts) {
ts->tv_sec = (long)ld;
ts->tv_nsec = (ld - ts->tv_sec) * 1000000000L;
if (ts->tv_nsec > 999999999L) {
@@ -167,12 +160,12 @@ INLINE void us_ld_to_timespec(long double ld, struct timespec *ts) {
}
}
INLINE long double us_timespec_to_ld(const struct timespec *ts) {
return ts->tv_sec + ((long double)ts->tv_nsec) / 1000000000;
INLINE ldf us_timespec_to_ld(const struct timespec *ts) {
return ts->tv_sec + ((ldf)ts->tv_nsec) / 1000000000;
}
INLINE int us_flock_timedwait_monotonic(int fd, long double timeout) {
const long double deadline_ts = us_get_now_monotonic() + timeout;
INLINE int us_flock_timedwait_monotonic(int fd, ldf timeout) {
const ldf deadline_ts = us_get_now_monotonic() + timeout;
int retval = -1;
while (true) {
@@ -188,15 +181,16 @@ INLINE int us_flock_timedwait_monotonic(int fd, long double timeout) {
}
INLINE char *us_errno_to_string(int error) {
locale_t locale = newlocale(LC_MESSAGES_MASK, "C", NULL);
char *buf;
if (locale) {
buf = us_strdup(strerror_l(error, locale));
freelocale(locale);
} else {
buf = us_strdup("!!! newlocale() error !!!");
char buf[2048];
const uz max_len = sizeof(buf) - 1;
# if (_POSIX_C_SOURCE >= 200112L) && ! _GNU_SOURCE
if (strerror_r(error, buf, max_len) != 0) {
US_SNPRINTF(buf, max_len, "Errno = %d", error);
}
return buf;
return us_strdup(buf);
# else
return us_strdup(strerror_r(error, buf, max_len));
# endif
}
INLINE char *us_signum_to_string(int signum) {

44
src/libs/types.h Normal file
View File

@@ -0,0 +1,44 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
typedef long long sll;
typedef ssize_t sz;
typedef int8_t s8;
typedef int16_t s16;
typedef int32_t s32;
typedef int64_t s64;
typedef unsigned uint;
typedef unsigned long long ull;
typedef size_t uz;
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
typedef long double ldf;

View File

@@ -22,6 +22,17 @@
#include "unjpeg.h"
#include <stdio.h>
#include <setjmp.h>
#include <assert.h>
#include <jpeglib.h>
#include <linux/videodev2.h>
#include "types.h"
#include "logging.h"
#include "frame.h"
typedef struct {
struct jpeg_error_mgr mgr; // Default manager
@@ -57,12 +68,12 @@ int us_unjpeg(const us_frame_s *src, us_frame_s *dest, bool decode) {
jpeg_start_decompress(&jpeg);
us_frame_copy_meta(src, dest);
dest->format = V4L2_PIX_FMT_RGB24;
dest->width = jpeg.output_width;
dest->height = jpeg.output_height;
dest->stride = jpeg.output_width * jpeg.output_components; // Row stride
dest->used = 0;
US_FRAME_COPY_META(src, dest); // cppcheck-suppress redundantAssignment
dest->format = V4L2_PIX_FMT_RGB24; // cppcheck-suppress redundantAssignment
dest->width = jpeg.output_width; // cppcheck-suppress redundantAssignment
dest->height = jpeg.output_height; // cppcheck-suppress redundantAssignment
dest->stride = jpeg.output_width * jpeg.output_components; // cppcheck-suppress redundantAssignment
dest->used = 0; // cppcheck-suppress redundantAssignment
if (decode) {
JSAMPARRAY scanlines;
@@ -77,9 +88,9 @@ int us_unjpeg(const us_frame_s *src, us_frame_s *dest, bool decode) {
jpeg_finish_decompress(&jpeg);
}
done:
jpeg_destroy_decompress(&jpeg);
return retval;
done:
jpeg_destroy_decompress(&jpeg);
return retval;
}
static void _jpeg_error_handler(j_common_ptr jpeg) {

View File

@@ -22,18 +22,7 @@
#pragma once
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <setjmp.h>
#include <assert.h>
#include <sys/types.h>
#include <jpeglib.h>
#include <linux/videodev2.h>
#include "logging.h"
#include "types.h"
#include "frame.h"

View File

@@ -26,11 +26,13 @@
#include <sys/ioctl.h>
#include "types.h"
#ifndef US_CFG_XIOCTL_RETRIES
# define US_CFG_XIOCTL_RETRIES 4
#endif
#define _XIOCTL_RETRIES ((unsigned)(US_CFG_XIOCTL_RETRIES))
#define _XIOCTL_RETRIES ((uint)(US_CFG_XIOCTL_RETRIES))
INLINE int us_xioctl(int fd, int request, void *arg) {

View File

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

View File

@@ -22,17 +22,19 @@
#pragma once
#include <stdio.h>
#include <stdbool.h>
#include <linux/videodev2.h>
#include "../libs/tools.h"
#include "../libs/logging.h"
#include "../libs/types.h"
#include "../libs/frame.h"
#include "../libs/unjpeg.h"
#include "data/blank_jpeg.h"
#include "../libs/frametext.h"
us_frame_s *us_blank_frame_init(const char *path);
typedef struct {
us_frametext_s *ft;
us_frame_s *raw;
us_frame_s *jpeg;
} us_blank_s;
us_blank_s *us_blank_init(void);
void us_blank_destroy(us_blank_s *blank);
void us_blank_draw(us_blank_s *blank, const char *text, uint width, uint height);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -1,723 +0,0 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "blank_jpeg.h"
const unsigned US_BLANK_JPEG_WIDTH = 640;
const unsigned US_BLANK_JPEG_HEIGHT = 480;
const size_t US_BLANK_JPEG_DATA_SIZE = 13845;
const uint8_t US_BLANK_JPEG_DATA[] = {
0xFF, 0xD8, 0xFF, 0xE1, 0x09, 0x50, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x6E, 0x73, 0x2E, 0x61, 0x64, 0x6F, 0x62,
0x65, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x78, 0x61, 0x70, 0x2F, 0x31, 0x2E, 0x30, 0x2F, 0x00, 0x3C, 0x3F, 0x78, 0x70, 0x61,
0x63, 0x6B, 0x65, 0x74, 0x20, 0x62, 0x65, 0x67, 0x69, 0x6E, 0x3D, 0x22, 0xEF, 0xBB, 0xBF, 0x22, 0x20, 0x69, 0x64, 0x3D,
0x22, 0x57, 0x35, 0x4D, 0x30, 0x4D, 0x70, 0x43, 0x65, 0x68, 0x69, 0x48, 0x7A, 0x72, 0x65, 0x53, 0x7A, 0x4E, 0x54, 0x63,
0x7A, 0x6B, 0x63, 0x39, 0x64, 0x22, 0x3F, 0x3E, 0x20, 0x3C, 0x78, 0x3A, 0x78, 0x6D, 0x70, 0x6D, 0x65, 0x74, 0x61, 0x20,
0x78, 0x6D, 0x6C, 0x6E, 0x73, 0x3A, 0x78, 0x3D, 0x22, 0x61, 0x64, 0x6F, 0x62, 0x65, 0x3A, 0x6E, 0x73, 0x3A, 0x6D, 0x65,
0x74, 0x61, 0x2F, 0x22, 0x20, 0x78, 0x3A, 0x78, 0x6D, 0x70, 0x74, 0x6B, 0x3D, 0x22, 0x41, 0x64, 0x6F, 0x62, 0x65, 0x20,
0x58, 0x4D, 0x50, 0x20, 0x43, 0x6F, 0x72, 0x65, 0x20, 0x35, 0x2E, 0x36, 0x2D, 0x63, 0x31, 0x33, 0x38, 0x20, 0x37, 0x39,
0x2E, 0x31, 0x35, 0x39, 0x38, 0x32, 0x34, 0x2C, 0x20, 0x32, 0x30, 0x31, 0x36, 0x2F, 0x30, 0x39, 0x2F, 0x31, 0x34, 0x2D,
0x30, 0x31, 0x3A, 0x30, 0x39, 0x3A, 0x30, 0x31, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x3E, 0x20, 0x3C,
0x72, 0x64, 0x66, 0x3A, 0x52, 0x44, 0x46, 0x20, 0x78, 0x6D, 0x6C, 0x6E, 0x73, 0x3A, 0x72, 0x64, 0x66, 0x3D, 0x22, 0x68,
0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x77, 0x77, 0x77, 0x2E, 0x77, 0x33, 0x2E, 0x6F, 0x72, 0x67, 0x2F, 0x31, 0x39, 0x39,
0x39, 0x2F, 0x30, 0x32, 0x2F, 0x32, 0x32, 0x2D, 0x72, 0x64, 0x66, 0x2D, 0x73, 0x79, 0x6E, 0x74, 0x61, 0x78, 0x2D, 0x6E,
0x73, 0x23, 0x22, 0x3E, 0x20, 0x3C, 0x72, 0x64, 0x66, 0x3A, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F,
0x6E, 0x20, 0x72, 0x64, 0x66, 0x3A, 0x61, 0x62, 0x6F, 0x75, 0x74, 0x3D, 0x22, 0x22, 0x2F, 0x3E, 0x20, 0x3C, 0x2F, 0x72,
0x64, 0x66, 0x3A, 0x52, 0x44, 0x46, 0x3E, 0x20, 0x3C, 0x2F, 0x78, 0x3A, 0x78, 0x6D, 0x70, 0x6D, 0x65, 0x74, 0x61, 0x3E,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x3F, 0x78, 0x70, 0x61, 0x63, 0x6B, 0x65, 0x74, 0x20, 0x65,
0x6E, 0x64, 0x3D, 0x22, 0x77, 0x22, 0x3F, 0x3E, 0xFF, 0xED, 0x00, 0x2C, 0x50, 0x68, 0x6F, 0x74, 0x6F, 0x73, 0x68, 0x6F,
0x70, 0x20, 0x33, 0x2E, 0x30, 0x00, 0x38, 0x42, 0x49, 0x4D, 0x04, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xD4, 0x1D,
0x8C, 0xD9, 0x8F, 0x00, 0xB2, 0x04, 0xE9, 0x80, 0x09, 0x98, 0xEC, 0xF8, 0x42, 0x7E, 0xFF, 0xDB, 0x00, 0x84, 0x00, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x02, 0x02, 0x02, 0x01, 0x02, 0x02, 0x03,
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0xFF, 0xDD, 0x00, 0x04, 0x00, 0x50, 0xFF, 0xEE, 0x00, 0x0E, 0x41, 0x64,
0x6F, 0x62, 0x65, 0x00, 0x64, 0xC0, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x01, 0xE0, 0x02, 0x80, 0x03,
0x00, 0x11, 0x00, 0x01, 0x11, 0x01, 0x02, 0x11, 0x01, 0xFF, 0xC4, 0x00, 0x7D, 0x00, 0x01, 0x00, 0x02, 0x03, 0x01, 0x01,
0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x0B, 0x07, 0x08, 0x09, 0x05, 0x06, 0x02, 0x03,
0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
0x01, 0x00, 0x01, 0x04, 0x02, 0x01, 0x03, 0x02, 0x03, 0x04, 0x08, 0x04, 0x06, 0x03, 0x00, 0x00, 0x00, 0x03, 0x02, 0x04,
0x05, 0x06, 0x01, 0x07, 0x08, 0x09, 0x11, 0x12, 0x0A, 0x13, 0x14, 0x21, 0x22, 0x15, 0x37, 0x77, 0xB6, 0x16, 0x23, 0x31,
0x35, 0x39, 0x41, 0x75, 0xB4, 0x17, 0x38, 0xB5, 0xB7, 0x18, 0x1A, 0x24, 0x32, 0x51, 0x78, 0x56, 0x97, 0xD4, 0x11, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xDA, 0x00, 0x0C,
0x03, 0x00, 0x00, 0x01, 0x11, 0x02, 0x11, 0x00, 0x3F, 0x00, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD0, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD1, 0xAF, 0xFC,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
0xFF, 0xD2, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x07, 0xFF, 0xD3, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD4, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD5, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD6, 0xAF, 0xFC,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
0xFF, 0xD7, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x07, 0xFF, 0xD0, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD1, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD2, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD3, 0xAF, 0xFC,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
0xFF, 0xD4, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x07, 0xFF, 0xD5, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD6, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD7, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD0, 0xAF, 0xFC,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
0xFF, 0xD1, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x07, 0xFF, 0xD2, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD3, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD4, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD5, 0xAF, 0xFC,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
0xFF, 0xD6, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x07, 0xFF, 0xD7, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD0, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD1, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD2, 0xAF, 0xFC,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
0xFF, 0xD3, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x06, 0x4C, 0xE9, 0x5B, 0x0B, 0x1C, 0xAF, 0x71, 0xF5, 0x2E, 0x2F, 0x29, 0x65, 0x69, 0x92, 0xC6, 0x64, 0xBB, 0x33,
0x43, 0xB0, 0xC8, 0xE3, 0xAF, 0xED, 0xA1, 0xBC, 0xB1, 0xBF, 0xB1, 0xBC, 0xDA, 0x71, 0x56, 0xF7, 0x76, 0x57, 0xB6, 0x97,
0x14, 0x49, 0x6F, 0x75, 0x69, 0x75, 0x6F, 0x25, 0x54, 0x49, 0x1D, 0x74, 0xD5, 0x45, 0x74, 0x55, 0xCD, 0x35, 0x71, 0xCF,
0x1C, 0xF3, 0xC0, 0x24, 0x45, 0xEB, 0x5F, 0xE9, 0x19, 0x61, 0xD4, 0x3F, 0xB6, 0x7C, 0xBB, 0xF1, 0x67, 0x4E, 0xB6, 0xC6,
0x75, 0x4C, 0x95, 0x47, 0x71, 0xDC, 0x5D, 0x53, 0xAA, 0xE3, 0x20, 0xB3, 0xC6, 0x75, 0x94, 0xF5, 0x71, 0x15, 0xBF, 0xF4,
0xE7, 0x4D, 0xC2, 0x63, 0xA0, 0x8A, 0x1B, 0x1D, 0x02, 0xF6, 0x5F, 0x6E, 0x72, 0x56, 0x50, 0x51, 0xF6, 0xF0, 0xD3, 0xD7,
0xF7, 0xE2, 0xA6, 0x9B, 0x0A, 0xEB, 0xA2, 0xC4, 0x23, 0x22, 0x0D, 0xD8, 0xF4, 0xE3, 0xEB, 0xED, 0x2F, 0xB5, 0xBC, 0xE3,
0xF1, 0xA7, 0xAE, 0xBB, 0x17, 0x5C, 0xC7, 0x6D, 0xBA, 0x46, 0xDD, 0xD9, 0x36, 0x38, 0x7D, 0x97, 0x5B, 0xCB, 0x47, 0x5C,
0xB8, 0xDC, 0xC6, 0x36, 0x6B, 0x0C, 0x85, 0x72, 0x59, 0xDD, 0xD1, 0x1C, 0x91, 0x49, 0xCC, 0x55, 0x57, 0x1D, 0x3C, 0xFE,
0x9A, 0xA9, 0xE7, 0x8E, 0x78, 0xE3, 0x9E, 0x39, 0x06, 0xF5, 0xFA, 0xF4, 0x78, 0xE9, 0xD2, 0x3E, 0x34, 0xF9, 0x4D, 0xD5,
0x7A, 0x6F, 0x44, 0x75, 0xBE, 0xBB, 0xD6, 0x3A, 0xBE, 0x6B, 0xA0, 0x30, 0xBB, 0x3E, 0x57, 0x0B, 0xAD, 0x45, 0x73, 0x0D,
0x95, 0xEE, 0x7E, 0xE3, 0xB1, 0x7B, 0x23, 0x15, 0x36, 0x52, 0x6A, 0x6E, 0xAE, 0x6E, 0x6B, 0xE6, 0xEA, 0x4C, 0x76, 0x26,
0xDA, 0x2E, 0x79, 0xE2, 0xAE, 0x38, 0xF8, 0x43, 0x4F, 0xE5, 0xFF, 0x00, 0xC8, 0x6B, 0xE7, 0xA6, 0xFF, 0x00, 0xA5, 0xD7,
0x6F, 0xFA, 0x83, 0xED, 0x57, 0xD7, 0xF8, 0xEB, 0xFE, 0x3A, 0xE7, 0xA3, 0xB5, 0x1C, 0x8C, 0x56, 0x3B, 0xCF, 0x6B, 0x64,
0x71, 0xF5, 0xE4, 0x3E, 0x59, 0x0A, 0xA0, 0xA6, 0xEB, 0x8D, 0x57, 0x49, 0xC4, 0x73, 0x35, 0xA5, 0x1B, 0x1E, 0xD3, 0x25,
0xBC, 0x91, 0xD7, 0x3F, 0xCA, 0x68, 0x6D, 0x31, 0xD6, 0xF2, 0xD3, 0x2C, 0xF5, 0xF3, 0x5D, 0x76, 0xF6, 0xF7, 0x01, 0xDA,
0xEE, 0xC7, 0x83, 0xD0, 0x8F, 0xD3, 0x07, 0x27, 0xFF, 0x00, 0x0E, 0x76, 0x0E, 0xA9, 0xAF, 0xCA, 0xCE, 0xF4, 0xD7, 0x61,
0xAE, 0xD3, 0x69, 0xC6, 0x65, 0x31, 0x98, 0xCE, 0xEE, 0xCB, 0x58, 0xE5, 0xE1, 0xB3, 0xBE, 0xB0, 0xA6, 0x1D, 0xEA, 0xDF,
0x71, 0xC9, 0xE0, 0xBA, 0x47, 0x57, 0xBF, 0xAA, 0x6B, 0xA9, 0x7F, 0x11, 0x8F, 0xB1, 0xB2, 0xFD, 0xA3, 0x67, 0x55, 0x31,
0xCD, 0x2D, 0x9F, 0xDC, 0x8E, 0xD6, 0xAE, 0x43, 0x15, 0xEA, 0xFE, 0xA3, 0x9E, 0x87, 0x9D, 0xD5, 0x2D, 0x3A, 0x2F, 0x73,
0xFA, 0x79, 0xEB, 0x1D, 0x25, 0x84, 0xCA, 0xDF, 0x5B, 0xC1, 0x46, 0xE7, 0xAB, 0x74, 0xDF, 0x5C, 0x5B, 0x59, 0x63, 0x20,
0xB8, 0xB7, 0xBB, 0xB4, 0xBA, 0xBE, 0xCD, 0xEC, 0x5D, 0x39, 0xCE, 0xAD, 0xD9, 0xD8, 0xCB, 0x7B, 0x5A, 0x2E, 0x7E, 0x54,
0xD1, 0x8C, 0xB4, 0xC8, 0xD7, 0x55, 0x5E, 0xD2, 0xF1, 0x4D, 0x32, 0x45, 0x1F, 0x20, 0xF1, 0xFC, 0xCA, 0xF4, 0x2E, 0xEB,
0xAD, 0xBF, 0xAB, 0x6B, 0xF2, 0x77, 0xD3, 0x43, 0x7A, 0xA7, 0xB2, 0xB4, 0x6B, 0xDC, 0x3D, 0xCE, 0xD3, 0x1F, 0x52, 0xF1,
0xB2, 0x45, 0xBA, 0xC1, 0x9C, 0xC2, 0x5A, 0xD1, 0x27, 0x37, 0x55, 0x75, 0x06, 0xEB, 0x45, 0x73, 0x64, 0x32, 0xB9, 0x1B,
0x0F, 0xC3, 0x57, 0x4D, 0x78, 0x3C, 0xBC, 0xB7, 0x59, 0x09, 0xAE, 0x28, 0x96, 0x28, 0xEE, 0xF9, 0xB9, 0xA6, 0x3B, 0x2A,
0xC2, 0x30, 0x12, 0x47, 0x24, 0x52, 0x57, 0x14, 0xB4, 0x57, 0x14, 0xB1, 0x57, 0x54, 0x72, 0x47, 0x25, 0x3C, 0xD1, 0x24,
0x72, 0x51, 0xCF, 0x34, 0xD7, 0x45, 0x74, 0x55, 0xC7, 0x15, 0x51, 0x5D, 0x15, 0x71, 0xCF, 0x1C, 0xF1, 0xCF, 0x1E, 0xFC,
0x72, 0x0F, 0xC0, 0x3A, 0x19, 0xE9, 0xA3, 0xE0, 0x76, 0x6B, 0xD4, 0x03, 0xC8, 0x9B, 0x6E, 0xAE, 0xE7, 0x33, 0x79, 0xAA,
0x75, 0xDE, 0xAB, 0x85, 0x97, 0x75, 0xED, 0x5D, 0xBB, 0x1F, 0x14, 0x32, 0x64, 0xF1, 0x7A, 0xAD, 0xB5, 0xF5, 0x9E, 0x3A,
0x1C, 0x56, 0xBD, 0xC5, 0xE4, 0x17, 0x18, 0xFA, 0xB6, 0x8D, 0x8F, 0x25, 0x7D, 0x1D, 0xBD, 0xA7, 0xDF, 0xA2, 0xB8, 0xE0,
0x8B, 0x89, 0xEE, 0xAA, 0x8E, 0x6A, 0x6D, 0xEA, 0x86, 0xB0, 0x90, 0x37, 0x92, 0x7D, 0xB9, 0xE8, 0xB9, 0xE9, 0x95, 0x9B,
0x8F, 0xC7, 0x5B, 0x0F, 0x0C, 0x74, 0x8F, 0x21, 0x3B, 0x3F, 0x09, 0x69, 0x8C, 0x9F, 0x70, 0xB4, 0xCB, 0xE9, 0x1A, 0x5F,
0x67, 0xDF, 0xEB, 0x52, 0x5E, 0x5B, 0x71, 0x91, 0x86, 0x9D, 0xC3, 0xB1, 0x7B, 0x82, 0xBC, 0xFD, 0xF5, 0xAE, 0xC9, 0x95,
0xB5, 0xC8, 0x53, 0x77, 0xC6, 0x3B, 0x19, 0x0C, 0x90, 0x43, 0x0C, 0x94, 0x53, 0x5D, 0x36, 0x91, 0xD3, 0x6F, 0x10, 0x3F,
0x5B, 0x0F, 0x40, 0xFA, 0x35, 0xFA, 0x99, 0x78, 0xCF, 0xDA, 0x3D, 0xD7, 0xD2, 0xB0, 0x6A, 0x9E, 0x21, 0x6D, 0x5D, 0x51,
0x87, 0x97, 0x2B, 0xB9, 0x6C, 0x18, 0xDD, 0x73, 0x0F, 0xD6, 0x37, 0xBD, 0x51, 0x27, 0x18, 0xDA, 0xB8, 0xC4, 0xCB, 0xD8,
0xFD, 0x51, 0xAF, 0xE4, 0xB9, 0xEB, 0xFD, 0x9B, 0x4F, 0xCE, 0xF3, 0x8E, 0xA7, 0xED, 0x5C, 0x62, 0x6A, 0xAA, 0x6B, 0xDB,
0x88, 0xE5, 0x86, 0xD6, 0xFA, 0x3B, 0xDE, 0x6E, 0xA1, 0xE4, 0x38, 0x81, 0xE8, 0xDF, 0xD6, 0xDD, 0x77, 0xD8, 0xFE, 0xA4,
0x7D, 0x35, 0xD7, 0xDD, 0x89, 0xAA, 0xE8, 0xFD, 0xB3, 0xA1, 0xDE, 0xDB, 0x77, 0x0C, 0x77, 0xD8, 0x3D, 0xC3, 0x56, 0xB1,
0xDA, 0x74, 0xDD, 0x8E, 0x8C, 0x3F, 0x53, 0xEF, 0x97, 0xD8, 0x9C, 0x8C, 0xDA, 0xDE, 0xE1, 0x88, 0xAE, 0x1B, 0x88, 0x63,
0xBE, 0xB2, 0x86, 0xF2, 0xD7, 0x8B, 0xBB, 0x3A, 0x27, 0x86, 0x5A, 0x28, 0xAF, 0x9A, 0x23, 0x96, 0x9F, 0x6A, 0x43, 0xEB,
0xFD, 0x72, 0xFA, 0xCB, 0xAD, 0xFA, 0x93, 0xCF, 0x7D, 0x8F, 0x4E, 0xEA, 0x9E, 0xBE, 0xD2, 0x3A, 0xCB, 0x51, 0x83, 0xAC,
0xFA, 0xDA, 0xFE, 0x0D, 0x5B, 0xAF, 0x75, 0x3C, 0x0E, 0x97, 0xAE, 0x43, 0x7D, 0x7D, 0x8B, 0xBA, 0x92, 0xFA, 0xF6, 0x2C,
0x1E, 0xB9, 0x61, 0x8D, 0xC6, 0x47, 0x77, 0x79, 0x25, 0x3C, 0x55, 0x2C, 0x9C, 0x45, 0xC5, 0x72, 0x73, 0xC7, 0xBD, 0x5C,
0xF3, 0xC8, 0x35, 0xA7, 0xC0, 0x5F, 0x4F, 0xEE, 0xE3, 0xF5, 0x01, 0xED, 0x59, 0xF4, 0x3E, 0xBA, 0xAE, 0xDB, 0x58, 0xD3,
0xF5, 0x98, 0x6D, 0x32, 0x5D, 0x99, 0xDA, 0x59, 0xAB, 0x39, 0xEF, 0x35, 0xED, 0x17, 0x11, 0x7B, 0x24, 0xD4, 0x59, 0x51,
0xC5, 0x94, 0x13, 0x5A, 0xCD, 0x9F, 0xD9, 0xF3, 0x55, 0x5B, 0x4B, 0x46, 0x3B, 0x17, 0x14, 0xD0, 0xD7, 0x73, 0x54, 0x52,
0x57, 0x24, 0xB0, 0x5B, 0x45, 0x3D, 0xC4, 0x41, 0x22, 0xCE, 0xC0, 0xEA, 0x0F, 0x43, 0xCF, 0x4A, 0x3B, 0x4C, 0x56, 0xA9,
0xDC, 0xDA, 0x87, 0xFE, 0x24, 0x7B, 0xD2, 0x28, 0xAC, 0xEF, 0xF2, 0x9A, 0xEE, 0xC5, 0x8C, 0xB0, 0xEE, 0x6E, 0xC0, 0xB8,
0x8E, 0x4B, 0x39, 0x66, 0x8A, 0xEF, 0x33, 0xA2, 0xE5, 0x32, 0x1A, 0xFF, 0x00, 0x4C, 0xE8, 0xF8, 0xA9, 0x69, 0xBC, 0xF9,
0xDA, 0xDA, 0xE4, 0x28, 0xB3, 0xBC, 0xBB, 0x86, 0x68, 0xA4, 0xF7, 0xBC, 0xA6, 0x1F, 0xBF, 0x18, 0x60, 0xAD, 0x2F, 0xD4,
0xD7, 0xD1, 0x2F, 0xB3, 0xAF, 0xE6, 0xD3, 0x3B, 0x67, 0xD3, 0x9F, 0x48, 0xE9, 0xDD, 0x77, 0x31, 0x73, 0x61, 0x04, 0x7B,
0xB6, 0x1B, 0xA1, 0x7A, 0x7F, 0x23, 0x6F, 0x8C, 0x8A, 0x2B, 0x9A, 0xAE, 0xE6, 0xBC, 0xCC, 0xE4, 0x3A, 0xDA, 0xC7, 0x0B,
0xD8, 0x98, 0x4B, 0x68, 0x6B, 0xB5, 0x86, 0x9E, 0x78, 0xC3, 0xDB, 0xE4, 0xA6, 0xB9, 0xA2, 0x49, 0x22, 0x92, 0x8E, 0x22,
0xF9, 0x71, 0x20, 0x63, 0x8F, 0x52, 0xDF, 0x4B, 0xCF, 0x07, 0x35, 0x5F, 0x19, 0x24, 0xF3, 0x77, 0xC3, 0x5E, 0xEE, 0xD6,
0x35, 0x8D, 0x06, 0xE3, 0x9C, 0x75, 0x38, 0x9D, 0x16, 0xF7, 0x74, 0x9F, 0x75, 0xD1, 0xBB, 0x0A, 0xEA, 0xFE, 0xEA, 0x58,
0xEB, 0xC2, 0x75, 0xB6, 0xC7, 0x7D, 0x79, 0x95, 0xDB, 0x71, 0xDB, 0xD5, 0xAD, 0x31, 0xCF, 0x54, 0xD8, 0x8B, 0xD9, 0x6F,
0x78, 0xA7, 0x9B, 0x49, 0x63, 0x93, 0xF0, 0x1C, 0xC1, 0x2F, 0x20, 0xC1, 0x3E, 0x85, 0x7E, 0x2D, 0x74, 0x8F, 0x97, 0x1B,
0x57, 0x96, 0x9D, 0x59, 0xDD, 0xFA, 0x46, 0x1B, 0x6A, 0xC3, 0xE4, 0x3A, 0x53, 0x0B, 0x6F, 0x84, 0xCD, 0x4F, 0x8E, 0xC7,
0x49, 0xB5, 0xE8, 0x99, 0x6C, 0x86, 0xCF, 0x55, 0xBD, 0x1B, 0x4E, 0x85, 0x9F, 0xBB, 0xB3, 0xBA, 0xBB, 0xD6, 0x76, 0x4B,
0x2A, 0xA9, 0xA2, 0xAA, 0x67, 0x83, 0xF4, 0x4D, 0x4D, 0x1F, 0x66, 0xE2, 0x89, 0xAD, 0xEB, 0x92, 0x1A, 0xC3, 0x9D, 0x3E,
0x77, 0x78, 0x45, 0xDA, 0x3E, 0x07, 0xF7, 0x96, 0x5B, 0xA9, 0xBB, 0x02, 0x19, 0x72, 0x9A, 0xFD, 0xF7, 0x17, 0x19, 0xAE,
0xB2, 0xEC, 0x2B, 0x7B, 0x4A, 0xE0, 0xC2, 0x76, 0x0E, 0x9D, 0xCD, 0xCD, 0x51, 0x5B, 0x64, 0xED, 0x3F, 0x54, 0xB1, 0xD9,
0x66, 0xF1, 0xD5, 0x73, 0x4C, 0x19, 0x4C, 0x7D, 0x55, 0xD5, 0x2D, 0x95, 0xD7, 0xF9, 0xD7, 0x04, 0xB6, 0xF3, 0xCC, 0x1A,
0x5A, 0x09, 0x44, 0xFA, 0x03, 0xF8, 0x73, 0xE3, 0x0F, 0x93, 0x7D, 0x45, 0xE4, 0x06, 0x6F, 0xBE, 0xFA, 0x5F, 0x4D, 0xED,
0x0C, 0xB6, 0xAF, 0xD8, 0xFA, 0xC6, 0x2B, 0x5F, 0xBF, 0xD9, 0xAD, 0xAF, 0x26, 0xB8, 0xC5, 0xE3, 0xAF, 0x75, 0x89, 0x6E,
0xEE, 0xAD, 0x2D, 0xAB, 0xB5, 0xBC, 0xB5, 0xF6, 0x86, 0x6B, 0x9A, 0x38, 0xAF, 0x9E, 0x2A, 0xE2, 0xAF, 0xD5, 0xFD, 0x9E,
0xDE, 0xFC, 0xFB, 0x84, 0x62, 0x33, 0x10, 0xC7, 0x6F, 0x97, 0xCA, 0xC1, 0x0D, 0x1C, 0x47, 0x0C, 0x19, 0x2B, 0xE8, 0x62,
0x8E, 0x9F, 0x7F, 0x6A, 0x23, 0x8E, 0xE6, 0x5A, 0x23, 0xA3, 0x8F, 0x7F, 0x7E, 0x7D, 0xA9, 0xA6, 0x9E, 0x38, 0x07, 0x9A,
0x09, 0x48, 0xFA, 0x14, 0x7A, 0x66, 0xF5, 0x9F, 0x79, 0xF4, 0xF7, 0x72, 0x79, 0x05, 0xE4, 0x87, 0x5E, 0x61, 0x77, 0x3D,
0x47, 0xB2, 0xB1, 0x99, 0xDE, 0x9B, 0xEA, 0x5C, 0x5E, 0xC9, 0x88, 0xB0, 0xC8, 0xFE, 0x03, 0x1B, 0x17, 0xCE, 0xDB, 0x7E,
0xEC, 0xDD, 0x66, 0x4C, 0x84, 0x17, 0xB1, 0xE2, 0x76, 0x5B, 0x4C, 0xD5, 0x11, 0x63, 0x30, 0xD9, 0x6B, 0x7A, 0x62, 0xBF,
0xC6, 0x5D, 0x63, 0xEF, 0xF9, 0x8A, 0xBA, 0x7E, 0xE7, 0x1C, 0x82, 0x3D, 0xDE, 0x53, 0x78, 0xF1, 0xB9, 0x78, 0xA7, 0xDF,
0xFD, 0x9F, 0xD0, 0x7B, 0xD4, 0x33, 0x7E, 0xD8, 0xEB, 0xDD, 0x9A, 0xF7, 0x17, 0x67, 0x93, 0x92, 0xD6, 0x4B, 0x4B, 0x7D,
0x9B, 0x5B, 0x96, 0xAF, 0xC5, 0xEA, 0xDB, 0x6E, 0x3A, 0x29, 0x3D, 0xF9, 0xE3, 0x1D, 0xB3, 0xE0, 0x27, 0xB7, 0xBD, 0x8B,
0x8F, 0x7E, 0x79, 0x8F, 0x89, 0xBE, 0xDD, 0x5E, 0xD5, 0xD1, 0x57, 0x1C, 0x06, 0xBF, 0x82, 0x57, 0x7F, 0x4E, 0xFF, 0x00,
0x8E, 0x9E, 0x3E, 0x77, 0x67, 0x4E, 0x79, 0x17, 0x94, 0xEE, 0x5E, 0x8A, 0xE9, 0xBE, 0xDB, 0xC9, 0xE1, 0x3B, 0x33, 0x54,
0xB0, 0xC2, 0xE4, 0x7B, 0x3B, 0xAC, 0x74, 0x9D, 0xF6, 0xFB, 0x11, 0x63, 0x71, 0xAB, 0x4F, 0x71, 0x71, 0x65, 0x8B, 0xBB,
0xDA, 0xB0, 0x79, 0x6B, 0x8C, 0x7D, 0xA4, 0xF7, 0x1C, 0x71, 0x5D, 0x71, 0xC5, 0x55, 0x14, 0x55, 0x5F, 0x1F, 0x2E, 0x78,
0xE7, 0x9F, 0xCC, 0x11, 0x57, 0xCD, 0xC7, 0x1C, 0x59, 0x9C, 0xBC, 0x51, 0x51, 0x44, 0x51, 0x45, 0x93, 0xBF, 0x8E, 0x38,
0xE3, 0xA7, 0x8A, 0x23, 0x8E, 0x3A, 0x2E, 0xA5, 0xA6, 0x8A, 0x28, 0xA2, 0x9E, 0x38, 0xA6, 0x8A, 0x28, 0xA7, 0x8E, 0x38,
0xE3, 0x8E, 0x38, 0xF6, 0xE3, 0x80, 0x79, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xD4, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x57, 0xE8, 0x6F, 0xDF, 0x97, 0x4C, 0xFF, 0x00, 0x15, 0xFA, 0xEF, 0xF9,
0xBF, 0x0E, 0x0B, 0x0F, 0x7C, 0xC4, 0xF3, 0x8B, 0xAA, 0x7C, 0x4D, 0xED, 0x0F, 0x1B, 0x7A, 0xDB, 0xBC, 0xEC, 0x31, 0x90,
0x75, 0x47, 0x93, 0x95, 0xF6, 0x96, 0x95, 0x9E, 0xDD, 0x33, 0x1F, 0x66, 0x6C, 0x26, 0x9F, 0x96, 0xC1, 0x45, 0xA0, 0xC1,
0x84, 0xE7, 0x6D, 0xB2, 0xBB, 0xA2, 0x4B, 0x09, 0x34, 0x5D, 0x82, 0x3D, 0xBE, 0xEA, 0xD3, 0x29, 0x3C, 0xDC, 0x73, 0x1D,
0x9F, 0x15, 0x45, 0x34, 0xBE, 0xD6, 0xB4, 0xDC, 0x55, 0x48, 0x45, 0x0F, 0xD6, 0x3B, 0xD2, 0xB2, 0xEF, 0xC3, 0xFD, 0xBA,
0x6E, 0xFE, 0xE8, 0xEC, 0x54, 0xF9, 0x2F, 0x17, 0x7B, 0x0B, 0x33, 0xF2, 0x92, 0xCA, 0xC2, 0x9A, 0xEE, 0xFF, 0x00, 0xE0,
0xBE, 0xD1, 0x99, 0x9A, 0xB9, 0xAD, 0xB5, 0x8B, 0xE9, 0x69, 0xAA, 0x5A, 0xEB, 0xD1, 0x33, 0x32, 0x57, 0xED, 0x83, 0xC8,
0x57, 0xCF, 0x34, 0xC3, 0x5F, 0x3C, 0x58, 0x5C, 0x55, 0xC4, 0xDF, 0x84, 0x96, 0xF4, 0x34, 0xC3, 0xD2, 0x8B, 0xFC, 0x46,
0x3C, 0x46, 0xFE, 0x2D, 0xE3, 0x3F, 0xE9, 0xD9, 0x30, 0x74, 0xFF, 0x00, 0xEA, 0x34, 0xD7, 0x72, 0x9B, 0x7F, 0x9E, 0x1E,
0x3B, 0x6A, 0x78, 0x48, 0xA8, 0xB8, 0xCD, 0x6D, 0x1E, 0x3F, 0xE9, 0x3A, 0xEE, 0x22, 0x09, 0x24, 0xA6, 0x18, 0xE6, 0xCA,
0x66, 0xBB, 0x93, 0xB4, 0x71, 0xB8, 0xF8, 0xAB, 0x96, 0xAF, 0xD3, 0x15, 0x12, 0x5D, 0xDC, 0xD1, 0xC7, 0x35, 0x73, 0xF9,
0x53, 0xC7, 0x3E, 0xE0, 0xEA, 0xF7, 0xA8, 0x47, 0x67, 0xD9, 0xFA, 0x4A, 0xFA, 0x63, 0xF5, 0xFF, 0x00, 0x49, 0x78, 0xF7,
0x3C, 0x78, 0x1D, 0xEB, 0x3D, 0x06, 0x2F, 0xA5, 0xB4, 0xCD, 0xA2, 0xCE, 0xDE, 0x2B, 0x7C, 0x95, 0xAE, 0x46, 0xFB, 0x15,
0x7F, 0x9E, 0xED, 0x4E, 0xD8, 0xAE, 0x88, 0xFE, 0xD7, 0x1C, 0x6C, 0xB9, 0x4A, 0xA1, 0xBB, 0x96, 0x39, 0xE9, 0xE7, 0xE5,
0x6B, 0x95, 0xCB, 0x43, 0x35, 0x34, 0xF3, 0x44, 0x3F, 0x00, 0x41, 0x76, 0x69, 0xA6, 0xB9, 0x9A, 0x5B, 0x8B, 0x89, 0x64,
0x9E, 0xE2, 0x79, 0x2B, 0x9A, 0x79, 0xE6, 0xAE, 0xB9, 0x66, 0x9A, 0x69, 0x6B, 0xE6, 0xB9, 0x65, 0x96, 0x5A, 0xF9, 0xAA,
0xB9, 0x24, 0x92, 0xBA, 0xB9, 0xE6, 0xAA, 0xB9, 0xE7, 0x9E, 0x79, 0xE7, 0x9F, 0x7E, 0x41, 0xFC, 0xC1, 0xDF, 0xBF, 0x40,
0x9F, 0x36, 0x76, 0xBE, 0x97, 0xF2, 0x73, 0x13, 0xE3, 0x0E, 0xC5, 0x99, 0xBC, 0xBC, 0xE9, 0xDF, 0x21, 0x6E, 0xAE, 0xB1,
0xD8, 0xEC, 0x3D, 0xD4, 0xF2, 0x4D, 0x65, 0xA9, 0x76, 0xBD, 0xAE, 0x36, 0x7B, 0xBD, 0x73, 0x3F, 0x88, 0x8A, 0x49, 0x79,
0xA2, 0xC3, 0x8D, 0xAE, 0x3B, 0x1E, 0x71, 0x17, 0xF1, 0xC3, 0x47, 0x1F, 0x8B, 0x96, 0x6B, 0x29, 0x24, 0xE7, 0xDA, 0xD6,
0x90, 0x7C, 0x6F, 0xAF, 0xBF, 0x8B, 0x9A, 0xF7, 0x40, 0x79, 0x9F, 0x47, 0x60, 0x69, 0x58, 0xEB, 0x6C, 0x4E, 0xA9, 0xE4,
0x7E, 0xB3, 0x37, 0x65, 0xDD, 0xE3, 0x2D, 0x22, 0xAE, 0x1B, 0x5B, 0x2E, 0xC5, 0xB5, 0xCB, 0x5C, 0xE2, 0xFB, 0x0E, 0xAB,
0x68, 0xFE, 0x1C, 0xC5, 0xF6, 0xF3, 0x97, 0x75, 0x5A, 0x66, 0x26, 0xE7, 0x8A, 0xF9, 0xE7, 0x9B, 0xDC, 0x9C, 0xFC, 0x7C,
0x28, 0xA3, 0x88, 0xF8, 0xA8, 0x38, 0x6C, 0x09, 0x2C, 0x7D, 0x34, 0x7D, 0x9B, 0xA8, 0x6B, 0x9E, 0x41, 0xF7, 0xF7, 0x58,
0x66, 0xB2, 0x56, 0x76, 0x1B, 0x5F, 0x66, 0xF5, 0xBE, 0xB5, 0x95, 0xD2, 0xED, 0xEE, 0xE6, 0xA2, 0x09, 0x33, 0x55, 0x75,
0xF6, 0x5F, 0x31, 0x75, 0xB0, 0xE2, 0x71, 0xBC, 0x49, 0x4F, 0x1F, 0x8B, 0xC9, 0x51, 0x8B, 0xD8, 0xF8, 0xBE, 0xFB, 0x14,
0x55, 0xF7, 0x39, 0xB4, 0xB2, 0x9E, 0x5E, 0x29, 0xAA, 0x88, 0x64, 0xAA, 0x80, 0xD3, 0x5F, 0x58, 0x8F, 0x07, 0x7C, 0x87,
0xE8, 0xCF, 0x2B, 0xBB, 0xCB, 0xBB, 0x36, 0x2D, 0x4F, 0x66, 0xDA, 0x7A, 0x67, 0xB8, 0xBB, 0x2F, 0x69, 0xEC, 0x6D, 0x5F,
0xB5, 0xF1, 0x76, 0x17, 0xB9, 0x8D, 0x67, 0x19, 0x1E, 0xE9, 0x97, 0xB9, 0xCE, 0x53, 0xA5, 0x6C, 0xF9, 0x3B, 0x68, 0xA6,
0x8B, 0x54, 0xCB, 0xEB, 0x72, 0xDE, 0x57, 0x61, 0x69, 0x6F, 0x7D, 0xCC, 0x1C, 0x5E, 0x5A, 0x5A, 0x53, 0x25, 0xAF, 0x32,
0x51, 0x4D, 0x7F, 0x6C, 0x38, 0xE2, 0x0E, 0xBD, 0x7A, 0x12, 0xFF, 0x00, 0x89, 0xEF, 0x8F, 0xBF, 0xE9, 0x3D, 0xCB, 0xFF,
0x00, 0x64, 0xFB, 0x04, 0x19, 0x07, 0xEA, 0x0D, 0xFF, 0x00, 0x11, 0xAD, 0xA3, 0xF8, 0x51, 0xD5, 0x9F, 0xF4, 0x8B, 0xC0,
0x77, 0x77, 0x44, 0xBF, 0xC6, 0x7A, 0x47, 0xFA, 0x2E, 0xE3, 0xBB, 0x13, 0x5D, 0xC2, 0xD9, 0xC3, 0xDC, 0x7B, 0x2E, 0x83,
0xAE, 0x6E, 0x57, 0x12, 0x5D, 0x58, 0x5B, 0xCD, 0x71, 0x91, 0xEF, 0x7E, 0xF1, 0x87, 0x17, 0x4E, 0x1E, 0x4D, 0x82, 0x2E,
0x7D, 0xAD, 0xF2, 0x36, 0x9D, 0x69, 0x8F, 0xC9, 0x5B, 0xC3, 0x24, 0x55, 0x57, 0xC5, 0x17, 0x16, 0x18, 0x1E, 0x63, 0xA7,
0x9E, 0x6B, 0x97, 0xDE, 0xA0, 0x84, 0x2E, 0xD1, 0xB4, 0x6C, 0x9B, 0xBE, 0xC9, 0x9D, 0xDC, 0x37, 0x0C, 0xEE, 0x57, 0x67,
0xDA, 0xB6, 0x7C, 0xAD, 0xF6, 0x73, 0x61, 0xD8, 0x73, 0x97, 0xD7, 0x19, 0x2C, 0xC6, 0x6B, 0x31, 0x92, 0xB8, 0x92, 0xEA,
0xFF, 0x00, 0x25, 0x92, 0xBF, 0xBA, 0x92, 0x5B, 0x8B, 0xBB, 0xCB, 0xBB, 0x89, 0x6A, 0xAE, 0xBA, 0xEB, 0xAB, 0x9A, 0xAA,
0xAB, 0x90, 0x78, 0x20, 0xF4, 0x79, 0xCC, 0x65, 0xAA, 0xC4, 0xC7, 0x80, 0xAB, 0x29, 0x91, 0xAB, 0x05, 0x16, 0x46, 0x6C,
0xC4, 0x58, 0x5E, 0x6F, 0x6E, 0x79, 0xC4, 0xC7, 0x96, 0xB8, 0xB6, 0x82, 0xCA, 0x7C, 0xA4, 0x78, 0xEE, 0x65, 0xFC, 0x1D,
0x19, 0x19, 0xEC, 0xED, 0xA3, 0x8A, 0xB9, 0xF8, 0xA3, 0x89, 0x6A, 0x8A, 0x3A, 0x69, 0xE6, 0xAE, 0x69, 0xA7, 0x8E, 0x38,
0x09, 0x2E, 0x7D, 0x32, 0x7F, 0xBF, 0x7F, 0x26, 0xFF, 0x00, 0x84, 0x9A, 0x9F, 0xF3, 0x8D, 0x40, 0xEB, 0x6E, 0xED, 0xB1,
0xF8, 0xBF, 0xEA, 0xF5, 0x8F, 0xF2, 0xB7, 0xC1, 0xAE, 0xCD, 0x82, 0xCB, 0x47, 0xEF, 0xBF, 0x1B, 0xBB, 0x6B, 0xB2, 0xF0,
0x1A, 0x96, 0x46, 0x3A, 0x60, 0xBC, 0xD8, 0xF0, 0xF6, 0x7A, 0x96, 0xDF, 0x93, 0xD6, 0x75, 0x5E, 0xE0, 0xD1, 0x3F, 0x15,
0x25, 0xBC, 0xD9, 0x7C, 0x3D, 0xD5, 0xBD, 0xBD, 0xBD, 0x86, 0xD1, 0x8B, 0xE2, 0x4A, 0x69, 0xE2, 0x49, 0x7E, 0x15, 0xF3,
0x0D, 0x37, 0x16, 0x17, 0x14, 0x84, 0x28, 0x7C, 0x99, 0xF1, 0xAF, 0xB5, 0x7C, 0x4A, 0xEE, 0x5D, 0xBB, 0xA3, 0xBB, 0x8B,
0x07, 0x56, 0x1B, 0x6D, 0xD5, 0x6E, 0xB8, 0xE6, 0x0B, 0xBB, 0x7F, 0xBB, 0x36, 0x0B, 0x69, 0xC0, 0x5D, 0x55, 0x25, 0x58,
0x5D, 0xBF, 0x55, 0xC8, 0xC9, 0x14, 0x3C, 0x65, 0x35, 0xCC, 0xED, 0xB4, 0x7C, 0xD7, 0x04, 0xBF, 0x1A, 0x24, 0x8A, 0x4A,
0x64, 0xB7, 0x9E, 0x88, 0x6E, 0x61, 0x9A, 0x18, 0xC2, 0x54, 0x7F, 0x4C, 0x97, 0xEE, 0x37, 0xC9, 0xEF, 0xE2, 0xBE, 0x9D,
0xFC, 0xA1, 0x70, 0x08, 0x7B, 0x67, 0xFF, 0x00, 0xBF, 0x73, 0x5F, 0xEA, 0xD9, 0x1F, 0xF7, 0x93, 0x03, 0x28, 0xF8, 0xED,
0xD1, 0x9B, 0x97, 0x92, 0xDD, 0xDF, 0xD6, 0x5D, 0x13, 0xA0, 0xC1, 0xCC, 0xBB, 0x47, 0x65, 0xED, 0x78, 0xDD, 0x72, 0xD2,
0xE3, 0x98, 0x6A, 0xB8, 0xB7, 0xC3, 0xD8, 0xCF, 0x5F, 0x33, 0xE7, 0x36, 0x4C, 0x84, 0x54, 0x57, 0x1D, 0x75, 0x62, 0xB5,
0x8C, 0x1D, 0xBD, 0xCE, 0x42, 0xEF, 0xE3, 0x57, 0x15, 0x7E, 0x1A, 0xDA, 0xBF, 0x8F, 0xBD, 0x5E, 0xDC, 0x72, 0x12, 0xE5,
0xF5, 0x32, 0xF3, 0xA6, 0xC7, 0xD2, 0xDB, 0x59, 0xF0, 0xCB, 0xC4, 0xDF, 0x18, 0x78, 0x86, 0xDE, 0xEB, 0xAF, 0x26, 0xD1,
0xB7, 0x2D, 0xDF, 0x09, 0xC4, 0xF1, 0xF1, 0x3D, 0xFF, 0x00, 0x4C, 0x68, 0xD7, 0x3F, 0xB2, 0xE0, 0xD3, 0xF3, 0xF7, 0x54,
0x53, 0x54, 0x94, 0xDF, 0xF7, 0x16, 0x56, 0xDA, 0xFE, 0x6C, 0x85, 0xDF, 0x14, 0xF1, 0x71, 0xFF, 0x00, 0xA4, 0x92, 0x5E,
0x7F, 0x3B, 0x9E, 0x2A, 0xE4, 0x30, 0xE7, 0xAF, 0x17, 0x8F, 0x9A, 0x8F, 0x92, 0xFE, 0x35, 0xF4, 0xAF, 0xA8, 0xFF, 0x00,
0x47, 0x51, 0x46, 0x77, 0x1F, 0x8C, 0xD5, 0x75, 0x8B, 0x4D, 0xC7, 0x23, 0x65, 0x6F, 0x4F, 0x17, 0x39, 0x7E, 0xA0, 0xDF,
0x2B, 0x8B, 0x23, 0xA5, 0x67, 0xF2, 0x50, 0xC3, 0xF7, 0x6B, 0x82, 0xFB, 0x49, 0xDA, 0x33, 0x35, 0x59, 0x5D, 0xC5, 0x57,
0x3C, 0xCB, 0x07, 0x19, 0x6A, 0xE9, 0x97, 0x9A, 0x78, 0xB4, 0xE7, 0x8A, 0x42, 0x22, 0xC0, 0x98, 0xFF, 0x00, 0xD3, 0x25,
0xFB, 0x8D, 0xF2, 0x7B, 0xF8, 0xAF, 0xA7, 0x7F, 0x28, 0x5C, 0x02, 0x1E, 0xD9, 0xFF, 0x00, 0xEF, 0xDC, 0xD7, 0xFA, 0xB6,
0x47, 0xFD, 0xE4, 0xC0, 0xF2, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0xFF, 0xD5, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x57, 0xE8, 0x6F, 0xDF, 0x97, 0x4C, 0xFF, 0x00, 0x15, 0xFA, 0xEF, 0xF9, 0xBF, 0x0E,
0x09, 0x43, 0xFD, 0x4F, 0xFF, 0x00, 0xDC, 0x5E, 0x17, 0x7F, 0xAB, 0x77, 0xF7, 0xFB, 0x3E, 0x9D, 0x06, 0x35, 0xF4, 0x75,
0xF5, 0x2C, 0xD3, 0xFB, 0x1B, 0x50, 0x87, 0xD3, 0x8F, 0xCD, 0x6A, 0xF1, 0x7B, 0x76, 0x89, 0xBA, 0x62, 0x64, 0xD0, 0xFA,
0x83, 0x66, 0xDE, 0x64, 0xA6, 0xF3, 0x17, 0x90, 0xC5, 0x5F, 0xDB, 0xD1, 0x63, 0x6B, 0xD2, 0x7B, 0x75, 0xCD, 0xE5, 0x5C,
0x7C, 0x6D, 0xA4, 0xA3, 0x8E, 0x28, 0xD6, 0x6F, 0x2B, 0x92, 0x99, 0x20, 0x9B, 0x8A, 0x2C, 0x28, 0xAE, 0x9E, 0x78, 0xB0,
0xA6, 0x80, 0xF8, 0x7C, 0x7F, 0xA6, 0xA6, 0xE3, 0xE0, 0x17, 0xAB, 0x8F, 0x89, 0x17, 0x78, 0x58, 0xF2, 0x5B, 0x27, 0x8E,
0x9D, 0x85, 0xDE, 0x11, 0x4B, 0xD5, 0x1B, 0xC4, 0xD4, 0x57, 0x71, 0x3E, 0x2A, 0x4E, 0x71, 0x99, 0x7B, 0xD9, 0x3A, 0xE7,
0x72, 0xB9, 0xA2, 0x2A, 0x61, 0x83, 0x6D, 0xC1, 0xDA, 0xD3, 0x5F, 0xD8, 0x9B, 0xF4, 0xC7, 0x98, 0xB1, 0x8B, 0xF1, 0x51,
0x53, 0x4C, 0x94, 0x5D, 0x5B, 0xDA, 0x87, 0xD9, 0x7A, 0xE9, 0x6C, 0xB8, 0x9D, 0x2F, 0xD5, 0x33, 0xC2, 0x3D, 0xC7, 0x3D,
0x2C, 0x30, 0x60, 0xF5, 0x3D, 0x17, 0xA4, 0xB6, 0x5C, 0xCC, 0xF7, 0x32, 0xC7, 0x05, 0xBC, 0x38, 0x9C, 0x17, 0x91, 0x5B,
0xEE, 0x53, 0x23, 0x2C, 0xF3, 0x4D, 0xFD, 0x4C, 0x30, 0xC7, 0x67, 0x6B, 0x5F, 0x35, 0x55, 0x5F, 0xE9, 0xA6, 0x9E, 0x39,
0xE7, 0x9F, 0xC8, 0x1B, 0x0F, 0xF5, 0x36, 0x68, 0xF9, 0xCC, 0xB7, 0x4D, 0xF8, 0xB7, 0xD9, 0x56, 0x34, 0x49, 0x36, 0xB5,
0xA8, 0x76, 0x26, 0xFF, 0x00, 0xA9, 0xE6, 0x64, 0x86, 0x3A, 0xE5, 0x86, 0x2C, 0x87, 0x60, 0x6B, 0x9A, 0xFE, 0x57, 0x01,
0x71, 0x34, 0xB4, 0x55, 0xCC, 0x71, 0x45, 0x54, 0x3D, 0x7F, 0x7B, 0x45, 0x35, 0x55, 0xC7, 0xB5, 0x55, 0x49, 0xC7, 0x1C,
0x55, 0xC7, 0x3C, 0xFB, 0x54, 0x10, 0xEA, 0x00, 0x1B, 0xDD, 0xE9, 0x87, 0xA2, 0xE7, 0xFB, 0x0F, 0xD4, 0x1B, 0xC4, 0x1C,
0x1E, 0xB9, 0x1C, 0xF2, 0x5E, 0xE2, 0xFB, 0xDB, 0x41, 0xDE, 0xAF, 0x79, 0x82, 0x89, 0xAB, 0xE6, 0x2C, 0x07, 0x5A, 0x66,
0xAD, 0xFB, 0x0B, 0x66, 0x92, 0x5E, 0x60, 0xA6, 0xAA, 0xA8, 0x83, 0xFA, 0x3F, 0xAC, 0x5C, 0xF1, 0x5D, 0x55, 0x7B, 0x51,
0xED, 0x57, 0xEA, 0xE7, 0x8E, 0x39, 0xE4, 0x1D, 0x98, 0xFA, 0x9C, 0xF6, 0x9C, 0x35, 0xDF, 0x6B, 0x78, 0xA1, 0xA4, 0xC1,
0x24, 0x1C, 0xEC, 0x1A, 0xFF, 0x00, 0x5E, 0xF6, 0x56, 0xD3, 0x93, 0x86, 0x9F, 0x87, 0xE2, 0x68, 0xC3, 0x6E, 0x3B, 0x26,
0xB5, 0x89, 0xC1, 0x49, 0x2F, 0xB7, 0x3F, 0x73, 0xEC, 0x4B, 0x7B, 0xA3, 0x64, 0x78, 0x8F, 0xDF, 0x8E, 0x29, 0xF9, 0x51,
0x5F, 0xB7, 0xBF, 0x3E, 0xFE, 0xC1, 0x17, 0x90, 0x7D, 0x36, 0x9B, 0xB9, 0xED, 0xBD, 0x77, 0xB5, 0x60, 0x37, 0x9D, 0x13,
0x64, 0xCC, 0xEA, 0x1B, 0x8E, 0xAD, 0x93, 0xB6, 0xCC, 0xEB, 0x9B, 0x36, 0xBD, 0x90, 0xB9, 0xC5, 0x66, 0xB0, 0xB9, 0x4B,
0x3A, 0xFE, 0xE5, 0xBD, 0xF6, 0x3F, 0x21, 0x67, 0x24, 0x57, 0x16, 0xD3, 0xC7, 0x57, 0xE5, 0xEF, 0x4D, 0x5F, 0xAA, 0x9E,
0x79, 0xA7, 0x9F, 0x7E, 0x39, 0xE7, 0x8E, 0x42, 0x45, 0x7E, 0x34, 0x7D, 0x47, 0xDD, 0xDF, 0xA7, 0xD9, 0xE3, 0xF5, 0x4F,
0x2A, 0x3A, 0xA7, 0x59, 0xEF, 0x0C, 0x04, 0x76, 0x3F, 0xB3, 0x6F, 0x77, 0x5D, 0x42, 0xBB, 0x7D, 0x0F, 0xB0, 0x2E, 0x69,
0xAA, 0x9A, 0x29, 0x92, 0xFF, 0x00, 0x3B, 0x87, 0xAA, 0xDA, 0xFB, 0x43, 0xD9, 0xA4, 0x96, 0x1E, 0x2B, 0x8A, 0xBB, 0x7B,
0x6B, 0x5C, 0x0D, 0x15, 0xFC, 0xF8, 0xAF, 0x99, 0x39, 0xE6, 0x9E, 0x69, 0x90, 0x3A, 0x21, 0xB8, 0xF8, 0x41, 0xE9, 0xB5,
0xEA, 0xFD, 0xD0, 0x9B, 0x17, 0x75, 0x78, 0x85, 0x8E, 0xD7, 0xBA, 0x77, 0xB8, 0x21, 0xAE, 0xFA, 0x9A, 0xB2, 0xFA, 0xCE,
0xBB, 0x69, 0xA3, 0x5D, 0xE1, 0xB7, 0xD9, 0xED, 0xE8, 0xC9, 0x73, 0xAD, 0xF7, 0x77, 0x59, 0xE1, 0xB9, 0xAF, 0x09, 0x77,
0x1E, 0x6E, 0x4E, 0x79, 0xAA, 0x4C, 0xB5, 0x8D, 0x32, 0x4F, 0x2D, 0x72, 0x57, 0x73, 0x6F, 0x7B, 0x77, 0x4D, 0x12, 0xC3,
0x28, 0x70, 0xFB, 0xD1, 0x67, 0x50, 0xD8, 0x7A, 0xF7, 0xD5, 0xBB, 0xAA, 0x74, 0x1D, 0xBB, 0x1D, 0x26, 0x23, 0x6B, 0xD1,
0xEF, 0xFC, 0x82, 0xD4, 0x36, 0x7C, 0x4C, 0xD5, 0x51, 0x5C, 0xD8, 0xBD, 0x87, 0x5A, 0xEA, 0x8E, 0xCB, 0xC2, 0xE6, 0xB1,
0xD2, 0xD7, 0x1D, 0x55, 0xC7, 0x54, 0x96, 0x59, 0x2B, 0x29, 0x62, 0xAB, 0x9A, 0x79, 0xE6, 0x9E, 0x79, 0xA7, 0xF2, 0xE7,
0x9E, 0x01, 0xED, 0xFD, 0x41, 0xBF, 0xE2, 0x35, 0xB4, 0x7F, 0x0A, 0x3A, 0xB3, 0xFE, 0x91, 0x78, 0x0E, 0xDB, 0xFA, 0xD6,
0x63, 0x2E, 0x3B, 0x67, 0xD2, 0x2B, 0xAA, 0xBB, 0x13, 0x47, 0x8F, 0x8B, 0x9D, 0x5B, 0x05, 0x9A, 0xF1, 0xDF, 0xB5, 0xAF,
0x2B, 0xB1, 0xF6, 0x9E, 0xDA, 0x8D, 0x2F, 0x62, 0xD2, 0x72, 0x5A, 0xBE, 0x2E, 0xE6, 0x3A, 0xED, 0x7D, 0xA1, 0xAA, 0xCE,
0x9C, 0x96, 0xFF, 0x00, 0x8E, 0xE7, 0x8A, 0xF8, 0xE3, 0xED, 0xFC, 0x79, 0xE3, 0x9E, 0x3D, 0xB8, 0xF6, 0xE7, 0x80, 0x84,
0x68, 0x24, 0x81, 0xA0, 0xFD, 0x47, 0xFD, 0xDF, 0xA0, 0xE8, 0x9A, 0x56, 0x89, 0x67, 0xE3, 0x7F, 0x55, 0x64, 0x2D, 0x34,
0xAD, 0x4B, 0x5C, 0xD4, 0xAD, 0x6F, 0xEE, 0x76, 0x9D, 0xBA, 0x2B, 0x9B, 0xDB, 0x6D, 0x73, 0x0F, 0x67, 0x87, 0x82, 0xEE,
0xE2, 0x38, 0xA9, 0xFB, 0x51, 0xCD, 0x73, 0x15, 0x9F, 0x15, 0xD7, 0x4D, 0x3F, 0xA7, 0x8A, 0xAA, 0xE7, 0x8E, 0x3F, 0x20,
0x77, 0xBB, 0xC6, 0x5F, 0x30, 0xF6, 0x5F, 0x39, 0xBD, 0x36, 0xBB, 0x9B, 0xBE, 0xB6, 0xCD, 0x3B, 0x07, 0xA2, 0xE6, 0x2F,
0xF4, 0xBE, 0xFF, 0x00, 0xD5, 0xEB, 0xC0, 0xEB, 0xB7, 0xD7, 0xF9, 0x1C, 0x6D, 0x10, 0x6B, 0x7A, 0x8E, 0x4E, 0x08, 0x2E,
0xA8, 0xB9, 0xC9, 0x71, 0xC5, 0xD7, 0x32, 0xDC, 0xD3, 0x3F, 0x3C, 0xD7, 0x4F, 0x3F, 0xA7, 0x8E, 0x78, 0xFC, 0x81, 0xC3,
0xEF, 0xA6, 0x4F, 0xF7, 0xEF, 0xE4, 0xDF, 0xF0, 0x93, 0x53, 0xFE, 0x71, 0xA8, 0x1C, 0xA7, 0xF2, 0xB3, 0xB8, 0xBB, 0x1B,
0xA0, 0x3D, 0x50, 0x7C, 0x9C, 0xED, 0xFE, 0xA6, 0xD9, 0xAF, 0xB5, 0x0D, 0xFF, 0x00, 0x47, 0xF2, 0xC7, 0xBA, 0x72, 0xD8,
0x1C, 0xD5, 0x85, 0x7F, 0x9D, 0x12, 0x71, 0xD8, 0x3B, 0x1C, 0x17, 0x76, 0x17, 0xD6, 0xF5, 0x7B, 0xC1, 0x91, 0xC3, 0xE5,
0xAC, 0x66, 0x96, 0xD6, 0xF6, 0xD2, 0x6A, 0x6B, 0x82, 0xEE, 0xD6, 0x69, 0x22, 0x92, 0x9A, 0xA8, 0xAF, 0x9E, 0x39, 0x09,
0x24, 0x66, 0xF1, 0xBE, 0x3F, 0xFD, 0x40, 0x3E, 0x16, 0xF1, 0x9C, 0xC1, 0x51, 0xAF, 0x75, 0xAF, 0x98, 0x7D, 0x3D, 0x67,
0xF6, 0x68, 0x86, 0x79, 0x2A, 0x92, 0xE7, 0x47, 0xDC, 0x2E, 0x20, 0xAA, 0x7A, 0xB0, 0x79, 0x39, 0x78, 0xA2, 0x5C, 0xC6,
0x5B, 0xA5, 0x3B, 0x32, 0xAB, 0x4A, 0xEA, 0xB4, 0xB9, 0xE2, 0x99, 0xA4, 0xC7, 0xDC, 0xD1, 0xCD, 0x7C, 0x71, 0x2D, 0xCD,
0x95, 0xCD, 0xBC, 0xE1, 0xFE, 0x7D, 0x3C, 0x5D, 0x61, 0xBE, 0x74, 0xBE, 0x9F, 0xE6, 0x67, 0x55, 0xF6, 0x76, 0xB5, 0x91,
0xD4, 0x37, 0xDD, 0x1B, 0xBC, 0x35, 0x7C, 0x0E, 0xCD, 0xAF, 0x65, 0x22, 0xFB, 0x77, 0x56, 0x17, 0xF6, 0xDA, 0x84, 0xB5,
0xD3, 0x55, 0x15, 0xD3, 0xCD, 0x50, 0xDD, 0xD8, 0xDE, 0xDB, 0x49, 0x1D, 0xC5, 0xAD, 0xCC, 0x35, 0x57, 0x6F, 0x77, 0x6B,
0x2C, 0x73, 0x43, 0x5D, 0x71, 0x49, 0x45, 0x75, 0x04, 0x2E, 0x73, 0xFF, 0x00, 0xDF, 0xB9, 0xAF, 0xF5, 0x6C, 0x8F, 0xFB,
0xC9, 0x81, 0x2A, 0xDF, 0x40, 0x5F, 0x1C, 0x75, 0x4E, 0x94, 0xE9, 0xEE, 0xEB, 0xF5, 0x22, 0xEE, 0xEE, 0x6D, 0xF0, 0x1A,
0xE6, 0x3F, 0x5A, 0xDA, 0xB5, 0xDE, 0xBF, 0xCC, 0x65, 0x21, 0xE7, 0xED, 0xE2, 0x3A, 0xFB, 0x4E, 0xA2, 0xBC, 0x9F, 0x6A,
0x6F, 0x36, 0x74, 0x55, 0x4D, 0x7F, 0x7E, 0xBC, 0xA6, 0x53, 0x13, 0x46, 0x1E, 0xCE, 0xA8, 0x7E, 0x37, 0x3C, 0x55, 0x8E,
0xBE, 0x83, 0x8E, 0x2A, 0xA6, 0xE7, 0x8E, 0x2A, 0x0F, 0xE9, 0xDE, 0x1E, 0x5E, 0x7D, 0x3E, 0xBE, 0x48, 0xF6, 0x66, 0xC3,
0xDC, 0x3D, 0xD9, 0xD6, 0x5D, 0xCF, 0xBD, 0xF6, 0x36, 0xD3, 0xC6, 0x3A, 0x9C, 0xDE, 0xC5, 0x77, 0x77, 0xE4, 0x16, 0x27,
0xF1, 0x11, 0x62, 0x31, 0x96, 0x98, 0x7C, 0x6D, 0xB5, 0xAE, 0x23, 0x5E, 0xED, 0xBC, 0x46, 0x0B, 0x15, 0x67, 0x67, 0x8E,
0xB1, 0x8A, 0x3A, 0x21, 0xB4, 0xB5, 0x82, 0x2E, 0x3E, 0x3C, 0xD5, 0xF1, 0xF9, 0xD5, 0x55, 0x55, 0x07, 0x4A, 0x3C, 0x1C,
0xF2, 0x83, 0xD3, 0x33, 0xC9, 0xDE, 0xB9, 0xD9, 0xBC, 0x01, 0xF1, 0xA6, 0xCF, 0x6D, 0xB4, 0xEB, 0xD8, 0xBA, 0xC7, 0x76,
0xA6, 0x4E, 0xB1, 0xEC, 0x28, 0xF7, 0x69, 0xA9, 0xBB, 0xD1, 0x76, 0x5C, 0x8C, 0x96, 0x9B, 0x7D, 0x96, 0xBB, 0xB0, 0x76,
0x06, 0xC7, 0xB5, 0x66, 0x2E, 0xE4, 0xB5, 0xBD, 0xDA, 0xF9, 0x9E, 0x8B, 0x5E, 0x2E, 0xF9, 0xAE, 0xD2, 0x89, 0x39, 0x96,
0xDE, 0x8A, 0x63, 0x86, 0xAE, 0x63, 0x08, 0x4F, 0xF9, 0x71, 0xE3, 0x7E, 0xD9, 0xE2, 0x57, 0x91, 0x3D, 0xA5, 0xD0, 0x5B,
0x85, 0x17, 0x12, 0x5E, 0x68, 0x5B, 0x2D, 0xD5, 0xA6, 0x1B, 0x2F, 0x34, 0x15, 0x41, 0x1E, 0xCF, 0xA8, 0xDE, 0xFB, 0x64,
0x35, 0x1D, 0xAA, 0xD7, 0x8E, 0x69, 0xA6, 0x3E, 0x60, 0xCF, 0xEB, 0xD7, 0x36, 0xF7, 0x15, 0x53, 0x47, 0x35, 0x71, 0x0C,
0xD5, 0x57, 0x17, 0x3C, 0xFC, 0xE3, 0xAB, 0x8E, 0x02, 0x52, 0x1F, 0x4C, 0x97, 0xEE, 0x37, 0xC9, 0xEF, 0xE2, 0xBE, 0x9D,
0xFC, 0xA1, 0x70, 0x08, 0x7B, 0x67, 0xFF, 0x00, 0xBF, 0x73, 0x5F, 0xEA, 0xD9, 0x1F, 0xF7, 0x93, 0x03, 0xC9, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD6, 0xAF,
0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x57,
0xE8, 0x6F, 0xDF, 0x97, 0x4C, 0xFF, 0x00, 0x15, 0xFA, 0xEF, 0xF9, 0xBF, 0x0E, 0x09, 0x43, 0xFD, 0x4F, 0xFF, 0x00, 0xDC,
0x5E, 0x17, 0x7F, 0xAB, 0x77, 0xF7, 0xFB, 0x3E, 0x9D, 0x04, 0x49, 0x23, 0x92, 0x48, 0xA4, 0xA2, 0x58, 0xAB, 0xAE, 0x29,
0x62, 0xAE, 0x99, 0x23, 0x92, 0x3A, 0xB9, 0xA2, 0x48, 0xE4, 0xA3, 0x9E, 0x2A, 0xA2, 0xBA, 0x2B, 0xA7, 0x9E, 0x2A, 0xA2,
0xBA, 0x2A, 0xE3, 0x8E, 0x78, 0xE7, 0x8E, 0x7D, 0xF8, 0xE4, 0x13, 0x4D, 0xF4, 0x6B, 0xF5, 0x44, 0xC0, 0x79, 0x59, 0xAF,
0xEA, 0xFE, 0x28, 0x79, 0x49, 0x79, 0x8D, 0xCC, 0x77, 0xE6, 0x85, 0x5D, 0x9E, 0x67, 0xAA, 0x37, 0x3D, 0xA2, 0x3B, 0x4B,
0x89, 0x7B, 0x52, 0xCB, 0x51, 0xA7, 0x9C, 0x8E, 0x36, 0x5E, 0x6E, 0x6E, 0xB8, 0xE7, 0x9E, 0x3B, 0x6B, 0x4A, 0xB3, 0xB7,
0xAA, 0x4E, 0x67, 0xA3, 0xE3, 0x73, 0x93, 0xC7, 0xC3, 0x55, 0xCF, 0x3C, 0xD7, 0x3C, 0x77, 0x92, 0x48, 0x1C, 0xDB, 0xFA,
0x96, 0xBF, 0xE7, 0x33, 0xA5, 0xFF, 0x00, 0xFA, 0xC7, 0xAF, 0xFF, 0x00, 0xDD, 0x5E, 0xD8, 0x07, 0x49, 0xFD, 0x3D, 0xBC,
0x99, 0xE8, 0xBF, 0x55, 0x8F, 0x08, 0x72, 0x5E, 0x08, 0x79, 0x29, 0x99, 0x8F, 0x8E, 0xEA, 0xD6, 0xF4, 0x5B, 0x6D, 0x52,
0xFE, 0x3B, 0xC9, 0xAC, 0x6D, 0x76, 0x4D, 0xAB, 0x09, 0xAB, 0x53, 0x4F, 0xF4, 0x1B, 0xB7, 0x7A, 0xFE, 0xE6, 0xF3, 0xEF,
0x71, 0x91, 0xDB, 0xB4, 0xF8, 0xAC, 0x2D, 0x2A, 0xCA, 0xD3, 0xF0, 0x92, 0x4E, 0x2E, 0xE0, 0xAA, 0x6B, 0x88, 0xEB, 0xB4,
0xBC, 0xAA, 0x9E, 0x42, 0x39, 0xDE, 0x64, 0xFA, 0x56, 0xF9, 0x75, 0xE1, 0xB6, 0xD1, 0x99, 0x83, 0x64, 0xEB, 0xBC, 0xF7,
0x62, 0xF5, 0x85, 0xBD, 0xC5, 0xD4, 0xB8, 0x0E, 0xE6, 0xEB, 0xAC, 0x16, 0x53, 0x3F, 0xA7, 0x64, 0xB0, 0xD1, 0xD7, 0x5D,
0x56, 0xB7, 0x5B, 0x1C, 0x58, 0xF8, 0xAF, 0xAF, 0x74, 0x1C, 0xBF, 0x36, 0xDC, 0x71, 0xF8, 0x8B, 0x2C, 0xA7, 0xDB, 0xA6,
0x99, 0xA9, 0x93, 0x8B, 0x69, 0xEE, 0xE0, 0xA6, 0x9B, 0x8A, 0xC3, 0x4D, 0xBA, 0x9B, 0xA1, 0xFB, 0xAB, 0xBE, 0x33, 0xD6,
0xFA, 0xCF, 0x4C, 0x75, 0x56, 0xFD, 0xD9, 0xD9, 0xAB, 0x8B, 0xB8, 0x2C, 0xBF, 0x0B, 0xA5, 0xEA, 0xF9, 0x7C, 0xF4, 0x76,
0x93, 0x4F, 0xF9, 0xD3, 0x5E, 0x56, 0xFA, 0xC6, 0xD6, 0x5B, 0x0C, 0x35, 0xA4, 0x71, 0xFB, 0xC9, 0x2D, 0xC5, 0xDC, 0xB0,
0x41, 0x0C, 0x54, 0xD5, 0x24, 0x95, 0xD3, 0x45, 0x3C, 0xD5, 0xC0, 0x4C, 0x2B, 0xD3, 0x9F, 0xC0, 0xDE, 0xB7, 0xF4, 0x90,
0xEA, 0x0D, 0xFF, 0x00, 0xCC, 0x8F, 0x34, 0xF7, 0x4D, 0x5B, 0x01, 0xDA, 0x97, 0x9A, 0xA4, 0xF8, 0xAB, 0xDA, 0x22, 0xC8,
0xDB, 0xE4, 0xF0, 0xFD, 0x65, 0xAB, 0x5C, 0xCB, 0x16, 0x47, 0x9D, 0x1F, 0x57, 0x9A, 0x0F, 0x7E, 0x77, 0x4E, 0xCF, 0xDC,
0xEF, 0xB1, 0xD0, 0x45, 0x37, 0xEC, 0xFA, 0x67, 0xF9, 0xD7, 0x15, 0x16, 0x56, 0x1C, 0xCB, 0x1D, 0x57, 0x13, 0xDD, 0x84,
0x55, 0xFC, 0xE9, 0xF2, 0xCB, 0x63, 0xF3, 0x5B, 0xC9, 0xBE, 0xC6, 0xEF, 0xDC, 0xED, 0xAD, 0xC6, 0x27, 0x1D, 0xB0, 0x5E,
0xC1, 0x89, 0xD1, 0xF5, 0x9B, 0x89, 0xA8, 0x9E, 0xBD, 0x4F, 0xAF, 0xF0, 0x31, 0xF3, 0x63, 0xAB, 0x60, 0xAB, 0x92, 0x2F,
0x78, 0x2B, 0xBE, 0xA6, 0xD3, 0x8A, 0xAE, 0xAF, 0xAB, 0x8F, 0xFA, 0xB9, 0x72, 0x37, 0x57, 0x12, 0x51, 0xC7, 0x14, 0xD7,
0xC7, 0x1C, 0x06, 0xA2, 0x03, 0xB0, 0x9E, 0x93, 0x1E, 0x9A, 0x3A, 0x07, 0xA8, 0x66, 0xC5, 0xDA, 0xB0, 0xF6, 0x07, 0x74,
0x73, 0xA0, 0xE3, 0x7A, 0xEB, 0x01, 0x1D, 0x76, 0xBA, 0x7E, 0xA1, 0x5E, 0x3A, 0x7E, 0xCB, 0xCC, 0x65, 0x33, 0x50, 0xCD,
0x6F, 0x88, 0xDA, 0x79, 0xB3, 0xCE, 0x63, 0xEE, 0xF1, 0x74, 0x68, 0x38, 0x2C, 0x97, 0x11, 0xD3, 0x7D, 0x54, 0x7C, 0x49,
0x73, 0x75, 0x35, 0x74, 0xDB, 0x71, 0x5D, 0x9F, 0x32, 0xD1, 0x72, 0x0D, 0x27, 0xF2, 0x9F, 0xC2, 0xDF, 0x21, 0xBC, 0x3E,
0xEC, 0x9C, 0x97, 0x5C, 0xF7, 0x1F, 0x5F, 0x67, 0x71, 0x9C, 0xC7, 0x97, 0xAB, 0x19, 0xAB, 0xEE, 0x36, 0x38, 0xCB, 0xEB,
0xDD, 0x23, 0x7F, 0xB7, 0x9E, 0xB9, 0xF9, 0xC5, 0x64, 0x74, 0xDD, 0x8E, 0x2B, 0x7E, 0x6C, 0x32, 0xBC, 0x65, 0x2D, 0xA0,
0xE6, 0x4E, 0x2D, 0x3E, 0x54, 0xDF, 0xDB, 0x55, 0xC5, 0x51, 0x5C, 0x43, 0x14, 0xD1, 0xD7, 0x1D, 0x21, 0x28, 0x7F, 0xA7,
0x97, 0xC4, 0x9E, 0xE8, 0xE8, 0x1D, 0x1F, 0xBD, 0x3B, 0xF7, 0xBA, 0x30, 0x39, 0x8E, 0xB1, 0xD6, 0x3B, 0x6B, 0x1B, 0xA5,
0x63, 0xB4, 0xAD, 0x67, 0x6F, 0x82, 0xE7, 0x5F, 0xCB, 0xE4, 0xF0, 0x1A, 0x7F, 0x3B, 0x1E, 0x63, 0x21, 0xD8, 0x39, 0xAC,
0x3E, 0x4E, 0x9B, 0x59, 0x70, 0xF8, 0x19, 0x28, 0xCE, 0xD1, 0x16, 0x2A, 0x6B, 0xAA, 0x68, 0x92, 0xE6, 0x0F, 0xC4, 0xDC,
0x53, 0x4D, 0x36, 0xB5, 0xDB, 0xCD, 0x70, 0x1C, 0xDF, 0xF4, 0xF6, 0xEC, 0x8D, 0x67, 0xB8, 0x3D, 0x7D, 0xB2, 0xBD, 0xA3,
0xA5, 0xD7, 0x44, 0xDA, 0x86, 0xF9, 0xDB, 0x5E, 0x58, 0xEC, 0xDA, 0xC5, 0xDD, 0x11, 0xD3, 0x17, 0x19, 0x1C, 0x0E, 0x53,
0x40, 0xED, 0x5B, 0x8C, 0x4E, 0x5A, 0xA8, 0xE9, 0xFC, 0xA8, 0x97, 0x2F, 0x63, 0x55, 0x17, 0x55, 0xF1, 0xEF, 0xCF, 0x3C,
0x57, 0x2F, 0x3E, 0xFC, 0xF3, 0xCF, 0xE7, 0xC8, 0x62, 0x7F, 0xA8, 0x37, 0xFC, 0x46, 0xB6, 0x8F, 0xE1, 0x47, 0x56, 0x7F,
0xD2, 0x2F, 0x01, 0xD5, 0x6F, 0x45, 0xFF, 0x00, 0x33, 0xFA, 0x77, 0xCA, 0x9F, 0x18, 0x72, 0x3E, 0x9B, 0x5E, 0x4B, 0xCB,
0x8A, 0xBD, 0xD8, 0xF1, 0xBA, 0x96, 0x7F, 0x47, 0xD3, 0x31, 0x5B, 0x15, 0xCC, 0x36, 0x90, 0x76, 0xB7, 0x51, 0x64, 0x6D,
0xEE, 0xE6, 0xA3, 0x5D, 0xC2, 0x5D, 0xFD, 0xC8, 0x25, 0xA3, 0x77, 0xEB, 0xAB, 0x69, 0x64, 0xA2, 0xDE, 0x3B, 0x7E, 0x63,
0xBC, 0x8F, 0x1B, 0x6D, 0x6D, 0x77, 0x6D, 0x55, 0x52, 0xDA, 0x5C, 0xCB, 0x10, 0x71, 0x87, 0xCE, 0xFF, 0x00, 0x47, 0x6F,
0x28, 0xFC, 0x41, 0xDC, 0x73, 0x39, 0x0D, 0x3F, 0x4A, 0xDA, 0xFB, 0xBB, 0xA2, 0x2E, 0xB2, 0x37, 0x32, 0x6A, 0x7D, 0x91,
0xA2, 0xE0, 0xEF, 0x36, 0x5C, 0xA6, 0x2B, 0x15, 0x24, 0xDE, 0xF6, 0x78, 0xFE, 0xCB, 0xD7, 0xB0, 0x36, 0x93, 0xE4, 0x35,
0x5C, 0xC5, 0xA5, 0x12, 0x47, 0x0C, 0x97, 0xB5, 0x41, 0x4E, 0x22, 0xF2, 0x5A, 0xA9, 0xFC, 0x3C, 0xFC, 0x49, 0x5D, 0x56,
0xD1, 0x07, 0x34, 0x3A, 0xEF, 0xA8, 0xBB, 0x53, 0xB7, 0x36, 0x28, 0xF5, 0x1E, 0xAD, 0xEB, 0x8D, 0xE3, 0xB0, 0xF6, 0x79,
0x2E, 0xA0, 0xB2, 0xFD, 0x85, 0xA6, 0x6A, 0xF9, 0x9D, 0x8F, 0x25, 0x0D, 0xC5, 0xCD, 0x72, 0xC7, 0x0D, 0x17, 0x76, 0xB8,
0xAB, 0x3B, 0x99, 0x2C, 0xA9, 0xE6, 0xAB, 0x79, 0x39, 0xE6, 0xB9, 0xB8, 0xA2, 0x8A, 0x69, 0x8E, 0xBA, 0xAA, 0xE7, 0x8E,
0x28, 0xAB, 0x9E, 0x02, 0x72, 0x5E, 0x13, 0xF4, 0x07, 0x60, 0x78, 0x5B, 0xE9, 0x1B, 0xD9, 0xFA, 0x3F, 0x91, 0x7C, 0x6B,
0xBA, 0x06, 0xCD, 0xFD, 0x02, 0xEF, 0xAD, 0xCF, 0x33, 0x63, 0x79, 0xB1, 0xE3, 0x24, 0xB6, 0xD5, 0xAC, 0xF7, 0x0D, 0x72,
0xFE, 0x3C, 0x3E, 0x2B, 0x61, 0xCC, 0xF1, 0x2D, 0x18, 0x3B, 0x7C, 0xDD, 0x55, 0xF1, 0x45, 0x32, 0x45, 0x05, 0xCD, 0xC4,
0x34, 0xC9, 0x35, 0x11, 0xF1, 0x2D, 0x52, 0x73, 0x55, 0x14, 0x87, 0x25, 0xBE, 0x99, 0x3F, 0xDF, 0xBF, 0x93, 0x7F, 0xC2,
0x4D, 0x4F, 0xF9, 0xC6, 0xA0, 0x71, 0x6B, 0xD4, 0x47, 0xFE, 0x7D, 0x7C, 0xCB, 0xFF, 0x00, 0xEC, 0xE7, 0x76, 0xFF, 0x00,
0xDC, 0x4D, 0x80, 0x1F, 0x19, 0xE2, 0x67, 0x95, 0x9D, 0xB1, 0xE1, 0xAF, 0x75, 0xEB, 0x1D, 0xDD, 0xD4, 0x39, 0x6F, 0xC2,
0x66, 0x70, 0xB3, 0x53, 0x69, 0x9E, 0xC0, 0x5D, 0xD7, 0x2F, 0x3A, 0xFE, 0xF3, 0xAA, 0x5C, 0x5C, 0x5B, 0xCB, 0x9A, 0xD3,
0x76, 0x7B, 0x48, 0xF9, 0xE3, 0x9B, 0x9C, 0x3E, 0x5E, 0x2B, 0x7A, 0x78, 0xF9, 0xD3, 0xED, 0x3D, 0xAC, 0xF4, 0x47, 0x71,
0x05, 0x71, 0xCF, 0x14, 0x72, 0x52, 0x16, 0x1F, 0xF8, 0x61, 0xE4, 0xE7, 0x45, 0xF9, 0x95, 0xD5, 0x16, 0x9E, 0x45, 0xF4,
0xCC, 0x18, 0xFB, 0x4B, 0xED, 0xBA, 0x2C, 0x6E, 0x0F, 0xB1, 0xF1, 0x72, 0x45, 0x63, 0x1E, 0xE3, 0xAC, 0x6D, 0x9A, 0xD5,
0xA5, 0x5E, 0xFA, 0x7E, 0xEB, 0x25, 0xAD, 0x34, 0xCB, 0x75, 0x79, 0x81, 0x87, 0x2B, 0xCD, 0x56, 0x53, 0xD7, 0xFD, 0x5D,
0xCD, 0x85, 0xCC, 0x53, 0xC3, 0xFD, 0x54, 0xB4, 0x71, 0xC0, 0x57, 0x61, 0xD1, 0x7D, 0x0F, 0xB8, 0x79, 0x3B, 0xE4, 0x9E,
0x91, 0xD0, 0xBA, 0x24, 0x5C, 0xD5, 0xB1, 0xF6, 0x67, 0x60, 0xD5, 0xAF, 0xC3, 0x79, 0xCC, 0x35, 0xDC, 0x41, 0x84, 0xC5,
0xF3, 0x79, 0x73, 0x79, 0xB1, 0x6C, 0xD7, 0xD1, 0x47, 0xCD, 0x32, 0x57, 0x8C, 0xD5, 0xF5, 0xEB, 0x4B, 0xAC, 0x8D, 0xCF,
0x14, 0xF3, 0xF2, 0xE6, 0xDE, 0xDA, 0xBF, 0x8F, 0xBD, 0x5E, 0xDC, 0x72, 0x12, 0x62, 0xF5, 0xD8, 0xEE, 0xFD, 0x4F, 0xC5,
0x6F, 0x15, 0xFA, 0x1B, 0xD3, 0x87, 0xA3, 0xE7, 0x8F, 0x0B, 0x8E, 0xCC, 0xEA, 0xF8, 0x1B, 0xAD, 0xC3, 0x1F, 0x6B, 0x73,
0x4F, 0x19, 0x3B, 0x0E, 0xA3, 0xEB, 0xF9, 0xAD, 0xEC, 0x75, 0x4B, 0x1C, 0xBD, 0x50, 0xF1, 0x0D, 0x53, 0xDD, 0x76, 0x1E,
0xE9, 0x8D, 0x9A, 0xFA, 0xEA, 0xE3, 0x9E, 0x3E, 0x77, 0x32, 0x61, 0xAE, 0x3E, 0xEF, 0x1C, 0xF1, 0x71, 0x57, 0x35, 0x04,
0x46, 0x01, 0xB0, 0xBE, 0x28, 0xF9, 0x09, 0xB2, 0xF8, 0xAB, 0xE4, 0x57, 0x52, 0xF7, 0xFE, 0xAB, 0xF7, 0xE5, 0xC8, 0x75,
0xBE, 0xDD, 0x61, 0x97, 0xBF, 0xC6, 0xDB, 0xCF, 0xCD, 0xBF, 0x39, 0xFD, 0x62, 0xE3, 0x89, 0x31, 0x9B, 0x7E, 0xB1, 0x24,
0xBE, 0xFC, 0x71, 0x44, 0x3B, 0x2E, 0xAF, 0x7D, 0x77, 0x63, 0x55, 0x5C, 0xFB, 0xF1, 0x47, 0x13, 0xFC, 0xBF, 0xB6, 0x9E,
0x01, 0x28, 0x5F, 0x5E, 0x8F, 0x1C, 0x75, 0x5F, 0x26, 0x3C, 0x62, 0xEA, 0x0F, 0x50, 0xBE, 0x93, 0xE2, 0x1D, 0x86, 0x8D,
0x4F, 0x59, 0xD7, 0x78, 0xD9, 0xB2, 0xD8, 0xC8, 0x38, 0xAE, 0x6D, 0x8F, 0xA3, 0x7B, 0x02, 0xB8, 0x32, 0x7A, 0xB6, 0x7A,
0xE2, 0x98, 0x63, 0x9A, 0xE2, 0xA9, 0x74, 0x7D, 0x9B, 0x35, 0x4F, 0xCE, 0x2F, 0xD3, 0xF8, 0x7B, 0x6C, 0xC5, 0xDC, 0x93,
0x73, 0xC7, 0x16, 0xDE, 0xD4, 0x87, 0xAB, 0xF4, 0xC9, 0x7E, 0xE3, 0x7C, 0x9E, 0xFE, 0x2B, 0xE9, 0xDF, 0xCA, 0x17, 0x00,
0x8D, 0x2E, 0x6F, 0xC0, 0xCF, 0x39, 0x65, 0xCC, 0xE5, 0xE5, 0x8B, 0xC3, 0x1F, 0x2B, 0xE4, 0x8A, 0x4C, 0x9D, 0xFC, 0x91,
0xC9, 0x1F, 0x8E, 0xBD, 0xBF, 0x5C, 0x72, 0x47, 0x5D, 0xD4, 0xB5, 0x51, 0x5D, 0x15, 0xD3, 0xA7, 0xF3, 0x4D, 0x74, 0x57,
0x4F, 0x3C, 0x73, 0xC7, 0x3C, 0x73, 0xED, 0xCF, 0x00, 0xC0, 0x7D, 0x99, 0xD3, 0x5D, 0xBF, 0xD2, 0xB9, 0x5C, 0x7E, 0x0B,
0xB9, 0x3A, 0xA7, 0xB2, 0x7A, 0x97, 0x39, 0x96, 0xC7, 0xFE, 0xD6, 0xC5, 0xE1, 0xBB, 0x33, 0x46, 0xD9, 0xF4, 0x3C, 0xAE,
0x4B, 0x15, 0xF8, 0x99, 0xAC, 0xFF, 0x00, 0x69, 0xE3, 0xF1, 0xDB, 0x4E, 0x2F, 0x15, 0x77, 0x79, 0x8F, 0xFC, 0x5D, 0xB4,
0x91, 0x7D, 0xE8, 0xE8, 0xAA, 0x3F, 0xB9, 0x1D, 0x54, 0xFC, 0xBE, 0x54, 0xF3, 0xC7, 0x01, 0x8D, 0x80, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xD7, 0xAF, 0xFC, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xB9, 0xAC, 0x6C,
0x39, 0x1D, 0x4B, 0x64, 0xD7, 0xB6, 0xBC, 0x47, 0x30, 0xD3, 0x96, 0xD6, 0x73, 0x98, 0x9D, 0x87, 0x17, 0x55, 0xC4, 0x5F,
0x7E, 0xDE, 0x9C, 0x8E, 0x16, 0xFE, 0xDF, 0x25, 0x65, 0xCC, 0xF0, 0xFC, 0xA9, 0xFB, 0xD0, 0xF1, 0x73, 0x6D, 0x4F, 0xCA,
0x9F, 0x7E, 0x3E, 0x54, 0xFB, 0xF1, 0xEE, 0x0D, 0xC8, 0xF3, 0x1F, 0xD4, 0x3B, 0xC9, 0x1F, 0x3B, 0x2D, 0xBA, 0xFA, 0xD7,
0xBF, 0xB2, 0xBA, 0x96, 0x4A, 0x1E, 0xB2, 0x9F, 0x67, 0xB8, 0xD5, 0xB8, 0xD6, 0x35, 0x4B, 0x3D, 0x6B, 0x98, 0x64, 0xDB,
0xA3, 0xC0, 0x47, 0x99, 0xE6, 0xF6, 0xAB, 0x59, 0xA5, 0xE6, 0xF3, 0x8A, 0xE9, 0xD6, 0xED, 0x7E, 0xDF, 0x15, 0x7B, 0x7D,
0xBF, 0x6A, 0xBD, 0xBF, 0xF7, 0x7E, 0x41, 0xA3, 0x60, 0xF5, 0xF5, 0xFD, 0x83, 0x39, 0xA9, 0xE7, 0xB0, 0xBB, 0x46, 0xB1,
0x97, 0xC9, 0x6B, 0xFB, 0x26, 0xB9, 0x95, 0xC7, 0xE7, 0x70, 0x19, 0xEC, 0x35, 0xE5, 0xC6, 0x3B, 0x2D, 0x85, 0xCC, 0xE2,
0x6E, 0xE2, 0xBE, 0xC6, 0x65, 0x71, 0x97, 0xF6, 0xB2, 0x45, 0x73, 0x65, 0x90, 0xC7, 0xDE, 0xC1, 0x44, 0xB0, 0xCB, 0x1D,
0x54, 0xD7, 0x1C, 0x94, 0x71, 0x55, 0x3C, 0xF1, 0xCF, 0x1C, 0x72, 0x0D, 0xB6, 0xF3, 0x53, 0xCD, 0xCE, 0xCC, 0xF3, 0x9F,
0x6D, 0xEA, 0xFD, 0xF7, 0xB6, 0xB1, 0x58, 0x1B, 0x3D, 0xCB, 0xAE, 0xFA, 0x87, 0x03, 0xD5, 0x37, 0xF9, 0x9C, 0x15, 0x13,
0x5B, 0xD3, 0xB9, 0x57, 0x84, 0xCE, 0x6C, 0x79, 0xD9, 0x76, 0xDC, 0x9D, 0x85, 0x7C, 0xFE, 0x13, 0x1B, 0x97, 0xCB, 0x5D,
0x6C, 0x72, 0x73, 0x3C, 0x16, 0xBC, 0x51, 0x6B, 0x4D, 0x54, 0x7B, 0xC7, 0x45, 0x14, 0xF3, 0xC5, 0x14, 0x86, 0xA6, 0x6B,
0xBB, 0x26, 0xC3, 0xA8, 0x67, 0x71, 0x5B, 0x46, 0xA5, 0x9E, 0xCD, 0x6A, 0xFB, 0x36, 0x0A, 0xF6, 0x0C, 0x9E, 0x0F, 0x62,
0xD7, 0x72, 0x97, 0xD8, 0x4C, 0xEE, 0x1B, 0x25, 0x6B, 0x5F, 0xCE, 0xDB, 0x21, 0x8A, 0xCB, 0xE3, 0x67, 0xB6, 0xC8, 0x63,
0xAF, 0x6D, 0xEB, 0xE3, 0xDE, 0x89, 0x61, 0x92, 0x89, 0x28, 0xE7, 0xF3, 0xE3, 0x9E, 0x01, 0xDC, 0xCE, 0x8E, 0xFA, 0x87,
0x7C, 0xE4, 0xEA, 0xFC, 0x3E, 0x33, 0x5D, 0xEC, 0x3C, 0x6F, 0x57, 0x77, 0xD6, 0x3B, 0x1D, 0x6F, 0xCD, 0xAF, 0xED, 0xCD,
0xD7, 0x03, 0x92, 0xD7, 0xF7, 0xDB, 0x88, 0x61, 0x86, 0x58, 0xEC, 0x68, 0x9F, 0x63, 0xD3, 0x32, 0xB8, 0x6C, 0x25, 0xE5,
0x70, 0x7B, 0xC7, 0xC4, 0xB3, 0xDD, 0x62, 0x2E, 0x6F, 0x2E, 0xA9, 0x8F, 0xDE, 0x59, 0xB9, 0x9A, 0xBA, 0xE6, 0xA8, 0x33,
0x2E, 0xD3, 0xF5, 0x30, 0x79, 0x3F, 0x7F, 0x87, 0x9E, 0xD7, 0x50, 0xE8, 0x2E, 0x8D, 0xD6, 0xF3, 0x52, 0xF3, 0xF0, 0x8B,
0x2F, 0x99, 0xBA, 0xDE, 0x76, 0x9B, 0x6B, 0x58, 0xAA, 0xA2, 0x4A, 0x64, 0xAE, 0x1C, 0x4D, 0xBE, 0x7B, 0x5A, 0xF9, 0xDE,
0x53, 0x55, 0x54, 0xD5, 0x15, 0x72, 0x4F, 0x5C, 0x54, 0xD5, 0x4F, 0xEB, 0x8A, 0x4E, 0x39, 0xF6, 0xE0, 0x38, 0xAD, 0xE4,
0xFF, 0x00, 0x99, 0xDE, 0x4A, 0xF9, 0x8B, 0xB4, 0xD1, 0xB4, 0xF9, 0x03, 0xDA, 0x59, 0xDD, 0xD7, 0xF0, 0x53, 0x4D, 0x2E,
0xBF, 0xAC, 0x51, 0xCC, 0x38, 0x6D, 0x17, 0x53, 0xA2, 0x6F, 0x78, 0xFE, 0xDE, 0xB1, 0xA5, 0xE1, 0xE2, 0xB3, 0xD7, 0xF1,
0x73, 0x7E, 0x1B, 0xE3, 0x0C, 0xB7, 0x9C, 0x41, 0x5E, 0x42, 0xF2, 0x88, 0xE8, 0xE6, 0xEA, 0xE2, 0x7A, 0xF8, 0xF9, 0xF2,
0x1A, 0xBA, 0x00, 0x32, 0x7F, 0x4E, 0x77, 0x4F, 0x6A, 0x78, 0xFB, 0xD8, 0x38, 0x3E, 0xD4, 0xE9, 0x8D, 0xE3, 0x39, 0xD7,
0xBB, 0xFE, 0xB9, 0x24, 0x95, 0xE2, 0xB6, 0x2C, 0x0C, 0xF1, 0xD1, 0x3D, 0x31, 0x4F, 0x4F, 0xDB, 0xBA, 0xB0, 0xBE, 0xB3,
0xBA, 0x8A, 0xE7, 0x1B, 0x98, 0xC4, 0x5F, 0xC5, 0xFA, 0x2E, 0x6C, 0xAF, 0x21, 0x9E, 0xD2, 0xE6, 0x3F, 0xD3, 0x2C, 0x75,
0xD3, 0xF9, 0x03, 0xBE, 0x5A, 0x57, 0xD4, 0xB5, 0xE5, 0x3E, 0x23, 0x03, 0x6D, 0x8F, 0xDE, 0x3A, 0x3B, 0xA4, 0x77, 0x4C,
0xE5, 0xB7, 0xC2, 0x2A, 0xB6, 0x1C, 0x6C, 0x9B, 0x96, 0xA1, 0xCD, 0xFC, 0x11, 0xDB, 0x5B, 0xC5, 0xC4, 0xD9, 0x1C, 0x4D,
0x19, 0xBC, 0xED, 0x9F, 0x39, 0x49, 0xEE, 0x28, 0x96, 0x59, 0x64, 0xB5, 0xE6, 0xD6, 0xDB, 0x9F, 0xB9, 0xC5, 0x31, 0xDB,
0xC5, 0xC5, 0x3F, 0xA8, 0x34, 0xF3, 0xCC, 0x7F, 0x5A, 0xFF, 0x00, 0x31, 0x7C, 0xBE, 0xD5, 0x33, 0x3D, 0x69, 0x35, 0xE6,
0xB1, 0xD3, 0x1D, 0x53, 0x9F, 0xB5, 0xE7, 0x1F, 0x9F, 0xD4, 0x3A, 0xB6, 0xDF, 0x29, 0x67, 0x94, 0xDA, 0xB1, 0x72, 0x53,
0xED, 0x73, 0x8C, 0xDB, 0x37, 0x2C, 0xBE, 0x4B, 0x21, 0x9C, 0xBE, 0xC6, 0x5E, 0x7C, 0xAB, 0x8E, 0xE2, 0xD2, 0xC7, 0xF6,
0x6D, 0x95, 0xDD, 0xB5, 0x5F, 0x66, 0xE6, 0x09, 0xE9, 0xF9, 0xF3, 0x58, 0x73, 0xDB, 0xC6, 0xCF, 0x22, 0xFB, 0x2F, 0xC5,
0x1E, 0xE3, 0xD5, 0xBB, 0xD7, 0xA8, 0x6E, 0xF1, 0x16, 0x5B, 0xFE, 0x9F, 0x06, 0x7E, 0xDF, 0x0B, 0x73, 0x9D, 0xC4, 0xC3,
0x9C, 0xC5, 0xD1, 0x1E, 0xCB, 0xAE, 0xE5, 0x35, 0x7C, 0xA7, 0x17, 0x18, 0xCB, 0x8A, 0xE3, 0x8A, 0x7E, 0x6B, 0xC5, 0x66,
0x26, 0xE2, 0x8E, 0x79, 0xE7, 0x8F, 0x85, 0x7C, 0xF1, 0x57, 0xF9, 0x03, 0xD6, 0xF2, 0x93, 0xCA, 0x4E, 0xD7, 0xF3, 0x0B,
0xB5, 0xEE, 0xFB, 0xA3, 0xBA, 0x2E, 0xF0, 0x77, 0xDB, 0xC5, 0xF6, 0x0F, 0x0B, 0xAF, 0x5C, 0x5C, 0x6B, 0xD8, 0x58, 0x70,
0x38, 0xEA, 0xF1, 0xD8, 0x18, 0x65, 0x83, 0x1D, 0xC7, 0x18, 0xE8, 0x25, 0x96, 0x2A, 0x26, 0xA2, 0x29, 0x79, 0xE2, 0xAA,
0xB8, 0xE7, 0x8F, 0x97, 0xB7, 0x1F, 0x97, 0xBF, 0xF6, 0x86, 0x00, 0xC7, 0x64, 0x72, 0x18, 0x7C, 0x85, 0x86, 0x5F, 0x11,
0x7F, 0x79, 0x8B, 0xCA, 0xE2, 0xEF, 0x2D, 0x72, 0x38, 0xCC, 0x9E, 0x3A, 0xEA, 0x7B, 0x1C, 0x86, 0x3B, 0x21, 0x63, 0x3C,
0x77, 0x36, 0x57, 0xF6, 0x17, 0xB6, 0xD2, 0x45, 0x73, 0x67, 0x79, 0x67, 0x73, 0x15, 0x32, 0x45, 0x2C, 0x75, 0x53, 0x5C,
0x75, 0xD3, 0xC5, 0x54, 0xF3, 0xC7, 0x3C, 0x71, 0xCF, 0x01, 0xDB, 0xBF, 0x1E, 0xBE, 0xA0, 0x2F, 0x3A, 0x7A, 0x5F, 0x13,
0x8E, 0xD6, 0x77, 0x8B, 0x8D, 0x17, 0xC8, 0x4D, 0x7E, 0xC2, 0x3A, 0x2D, 0xE3, 0xBD, 0xED, 0x0C, 0x56, 0x52, 0x1D, 0xF6,
0x3B, 0x48, 0x21, 0xB9, 0xA6, 0x18, 0xA3, 0xDE, 0x35, 0x8C, 0xB6, 0x1A, 0x5C, 0x94, 0xF5, 0x5C, 0x4B, 0x1D, 0x53, 0x5C,
0xE5, 0xEC, 0xF2, 0xD7, 0x52, 0xD1, 0x17, 0xC7, 0xEE, 0x53, 0x55, 0x5C, 0xD7, 0xC0, 0x6C, 0x9E, 0x67, 0xEA, 0x67, 0xF2,
0x42, 0x7C, 0x65, 0xDC, 0x5A, 0xFF, 0x00, 0x8E, 0x7D, 0x23, 0x8C, 0xCC, 0x57, 0x45, 0x1C, 0x58, 0xDF, 0xE6, 0x72, 0xDB,
0xE6, 0x77, 0x19, 0x6F, 0x27, 0x12, 0xD1, 0xCC, 0x95, 0x5D, 0xE2, 0x6C, 0xB3, 0x1A, 0xED, 0xD5, 0xE5, 0x15, 0x43, 0xC5,
0x54, 0xF1, 0x4D, 0x17, 0xB0, 0x73, 0x4D, 0x5C, 0xF1, 0x57, 0xBF, 0x3C, 0x71, 0xCD, 0x35, 0x07, 0x1F, 0x7C, 0xB3, 0xF5,
0x0C, 0xF2, 0xCB, 0xCD, 0x5B, 0xC8, 0x69, 0xEF, 0x4E, 0xCF, 0xBE, 0xC8, 0xEA, 0x96, 0x17, 0x3C, 0x5E, 0x61, 0xFA, 0xDB,
0x59, 0xB6, 0x8B, 0x55, 0xEB, 0x9C, 0x4D, 0xCD, 0x14, 0xF1, 0xC4, 0x77, 0x54, 0x6B, 0x58, 0xCE, 0x68, 0xA3, 0x33, 0x91,
0x83, 0x9E, 0x6B, 0xE6, 0x2B, 0xDC, 0xA4, 0xB7, 0xF7, 0xD0, 0xD3, 0x2D, 0x74, 0x47, 0x35, 0x31, 0xD5, 0xF0, 0xE0, 0x3C,
0x9F, 0x0F, 0xBC, 0xE5, 0xEF, 0xBF, 0x06, 0xB6, 0x4D, 0xC7, 0x6B, 0xE8, 0x4C, 0x86, 0xB1, 0x8E, 0xCC, 0x6F, 0x58, 0x3B,
0x0D, 0x7B, 0x3D, 0x2E, 0xCD, 0xAD, 0xDB, 0x6C, 0x90, 0xD7, 0x8D, 0xC7, 0x5F, 0xD5, 0x92, 0xB7, 0x8E, 0xD6, 0x0B, 0x99,
0xA2, 0xA2, 0xDA, 0x5F, 0xC5, 0x55, 0xEF, 0x55, 0x7C, 0x7B, 0xF3, 0xCF, 0x1C, 0x7B, 0x7E, 0x5F, 0x9F, 0xB8, 0x6B, 0xDF,
0x6A, 0xF6, 0x56, 0xD1, 0xDC, 0x9D, 0x99, 0xBF, 0xF6, 0xD6, 0xED, 0x35, 0x9D, 0xC6, 0xE1, 0xD9, 0x7B, 0x8E, 0xC5, 0xBD,
0x6D, 0x13, 0xE3, 0xEC, 0xE8, 0xC7, 0xD8, 0x4D, 0x9F, 0xDA, 0x32, 0xB7, 0x59, 0x9C, 0xAC, 0x96, 0x76, 0x31, 0x73, 0x54,
0x76, 0x76, 0xB5, 0xDE, 0xDE, 0x57, 0xCD, 0x11, 0x53, 0xCF, 0x3C, 0x51, 0x4F, 0xB7, 0x1F, 0xE4, 0x0F, 0x81, 0x06, 0xED,
0xF8, 0x2B, 0xE7, 0x97, 0x73, 0x78, 0x0D, 0xDA, 0xB7, 0x3D, 0x8B, 0xD5, 0x92, 0xDA, 0xE6, 0xB0, 0xBB, 0x06, 0x3B, 0x9C,
0x46, 0xF9, 0xD7, 0x19, 0xEB, 0x9B, 0xD8, 0xF5, 0x2D, 0xE3, 0x1F, 0x0C, 0x77, 0x5C, 0xE2, 0x6B, 0xC9, 0xC5, 0x67, 0x25,
0x13, 0x5A, 0xE5, 0xF5, 0xFB, 0xDB, 0xAA, 0xA7, 0xB0, 0xBD, 0x8B, 0xDA, 0x7B, 0x7E, 0x6B, 0x96, 0x2F, 0x7A, 0xA0, 0x9E,
0x78, 0xE4, 0x0F, 0x94, 0xF1, 0x5F, 0xCC, 0xBE, 0xE1, 0xF0, 0xDF, 0xB3, 0x76, 0x1E, 0xDD, 0xE9, 0x58, 0xB4, 0xBB, 0x5D,
0xDF, 0x62, 0xC1, 0x64, 0x75, 0xB9, 0x32, 0x7B, 0x5E, 0xAB, 0x67, 0xB5, 0x7E, 0xCB, 0xC4, 0xE5, 0xB2, 0x76, 0x79, 0x5C,
0x84, 0x58, 0x5A, 0x2F, 0xA4, 0xA3, 0xF6, 0x6D, 0xCD, 0xDC, 0xD6, 0x11, 0x47, 0x24, 0xD4, 0x73, 0xF7, 0x2A, 0x87, 0x8E,
0x63, 0xF7, 0xF8, 0xD7, 0x5F, 0x15, 0x07, 0xC0, 0x79, 0x19, 0xE4, 0x4F, 0x6A, 0x79, 0x55, 0xDB, 0xBB, 0x3F, 0x77, 0x77,
0x2E, 0x72, 0x2C, 0xF6, 0xF7, 0xB5, 0xD3, 0x8B, 0x86, 0xFA, 0x7B, 0x3B, 0x4A, 0x31, 0xB8, 0xAB, 0x2B, 0x2C, 0x2E, 0x2E,
0xD3, 0x0F, 0x8C, 0xC6, 0xE1, 0xB1, 0x30, 0xD5, 0x55, 0xB6, 0x2F, 0x1D, 0x6B, 0x67, 0x65, 0x4F, 0xB4, 0x51, 0xFB, 0x71,
0x54, 0xB5, 0x57, 0x2D, 0x5F, 0x29, 0x24, 0xAE, 0xAA, 0x83, 0x07, 0x00, 0x0E, 0x8F, 0xF5, 0x2F, 0xAA, 0xA7, 0x96, 0xFD,
0x3B, 0xE3, 0x8C, 0xFE, 0x29, 0x60, 0x73, 0x3A, 0x2E, 0xC5, 0xD2, 0x97, 0x18, 0x3D, 0xBB, 0x57, 0x93, 0x5A, 0xDE, 0xF4,
0x7C, 0x76, 0xD7, 0x2F, 0xF4, 0x67, 0x78, 0xAF, 0x23, 0x26, 0xC1, 0xAF, 0x7E, 0x3A, 0xF6, 0x6A, 0x65, 0xAB, 0x11, 0x34,
0x99, 0x7B, 0x9E, 0x62, 0x8A, 0xAE, 0x2A, 0xE2, 0x1E, 0x26, 0xE6, 0x9A, 0x3D, 0xA9, 0xE2, 0x9A, 0x69, 0x0F, 0x99, 0xF0,
0xFB, 0xD4, 0x9F, 0xC9, 0xEF, 0x06, 0xB5, 0xBD, 0xC7, 0x54, 0xE8, 0x4C, 0xAE, 0x9B, 0x8D, 0xC4, 0xEF, 0x59, 0xCB, 0x1D,
0x87, 0x60, 0xA7, 0x66, 0xD4, 0x6C, 0xF6, 0x4B, 0x89, 0x72, 0x38, 0xEB, 0x0E, 0x71, 0xB6, 0xBC, 0xDB, 0x4D, 0x75, 0x3C,
0x5F, 0x86, 0x86, 0x8B, 0x6A, 0xB9, 0xF7, 0xA6, 0x9E, 0x3F, 0x55, 0x5C, 0xFB, 0xF3, 0xCF, 0xF6, 0x7B, 0x06, 0xE1, 0x7F,
0xE6, 0x0D, 0xF5, 0x1A, 0xFF, 0x00, 0xF2, 0x8E, 0xA8, 0xFF, 0x00, 0xF5, 0x66, 0x23, 0xFF, 0x00, 0xEB, 0x07, 0x3E, 0x7C,
0xBE, 0xF3, 0x5F, 0xBD, 0x3C, 0xE1, 0xDD, 0x75, 0x8D, 0xFB, 0xBE, 0x72, 0x3A, 0xE6, 0x47, 0x61, 0xD4, 0x75, 0x7F, 0xE8,
0x7E, 0x1A, 0x4D, 0x6B, 0x5D, 0xB6, 0xD6, 0xED, 0x23, 0xC3, 0x7E, 0xD6, 0xC8, 0x66, 0xBE, 0x13, 0xDA, 0xDA, 0xCB, 0x2D,
0x33, 0xDC, 0x7E, 0x3B, 0x27, 0x2F, 0x3F, 0x73, 0x9E, 0x7D, 0xFE, 0x3E, 0xDC, 0x7F, 0x90, 0x35, 0x28, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD0, 0xAF, 0xFC, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF,
0xD1, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x07, 0xFF, 0xD2, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD3, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD4, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD5, 0xAF, 0xFC, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF,
0xD6, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x07, 0xFF, 0xD7, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD0, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD1, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD2, 0xAF, 0xFC, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF,
0xD3, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x07, 0xFF, 0xD4, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD5, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD6, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD7, 0xAF, 0xFC, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF,
0xD0, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x07, 0xFF, 0xD1, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD2, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD3, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD4, 0xAF, 0xFC, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF,
0xD5, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x07, 0xFF, 0xD6, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD7, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD0, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD1, 0xAF, 0xFC, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF,
0xD2, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x07, 0xFF, 0xD9,
};

View File

@@ -1,900 +0,0 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "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; // cppcheck-suppress unusedStructMember
const unsigned format; // cppcheck-suppress unusedStructMember
} _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; // cppcheck-suppress unusedStructMember
const enum v4l2_memory io_method; // cppcheck-suppress unusedStructMember
} _IO_METHODS[] = {
{"MMAP", V4L2_MEMORY_MMAP},
{"USERPTR", V4L2_MEMORY_USERPTR},
};
static int _device_open_check_cap(us_device_s *dev);
static int _device_open_dv_timings(us_device_s *dev);
static int _device_apply_dv_timings(us_device_s *dev);
static int _device_open_format(us_device_s *dev, bool first);
static void _device_open_hw_fps(us_device_s *dev);
static void _device_open_jpeg_quality(us_device_s *dev);
static int _device_open_io_method(us_device_s *dev);
static int _device_open_io_method_mmap(us_device_s *dev);
static int _device_open_io_method_userptr(us_device_s *dev);
static int _device_open_queue_buffers(us_device_s *dev);
static int _device_apply_resolution(us_device_s *dev, unsigned width, unsigned height);
static void _device_apply_controls(us_device_s *dev);
static int _device_query_control(
us_device_s *dev, struct v4l2_queryctrl *query,
const char *name, unsigned cid, bool quiet);
static void _device_set_control(
us_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(x_next) dev->run->x_next
#define _D_XIOCTL(...) us_xioctl(_RUN(fd), __VA_ARGS__)
us_device_s *us_device_init(void) {
us_device_runtime_s *run;
US_CALLOC(run, 1);
run->fd = -1;
us_device_s *dev;
US_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 = us_get_cores_available() + 1;
dev->min_frame_size = 128;
dev->timeout = 1;
dev->run = run;
return dev;
}
void us_device_destroy(us_device_s *dev) {
free(dev->run);
free(dev);
}
int us_device_parse_format(const char *str) {
US_ARRAY_ITERATE(_FORMATS, 0, item, {
if (!strcasecmp(item->name, str)) {
return item->format;
}
});
return US_FORMAT_UNKNOWN;
}
v4l2_std_id us_device_parse_standard(const char *str) {
US_ARRAY_ITERATE(_STANDARDS, 1, item, {
if (!strcasecmp(item->name, str)) {
return item->standard;
}
});
return US_STANDARD_UNKNOWN;
}
int us_device_parse_io_method(const char *str) {
US_ARRAY_ITERATE(_IO_METHODS, 0, item, {
if (!strcasecmp(item->name, str)) {
return item->io_method;
}
});
return US_IO_METHOD_UNKNOWN;
}
int us_device_open(us_device_s *dev) {
if ((_RUN(fd) = open(dev->path, O_RDWR|O_NONBLOCK)) < 0) {
US_LOG_PERROR("Can't open device");
goto error;
}
US_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);
US_LOG_DEBUG("Device fd=%d initialized", _RUN(fd));
return 0;
error:
us_device_close(dev);
return -1;
}
void us_device_close(us_device_s *dev) {
_RUN(persistent_timeout_reported) = false;
if (_RUN(hw_bufs) != NULL) {
US_LOG_DEBUG("Releasing device buffers ...");
for (unsigned index = 0; index < _RUN(n_bufs); ++index) {
# define HW(x_next) _RUN(hw_bufs)[index].x_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) != NULL) {
if (munmap(HW(raw.data), HW(raw.allocated)) < 0) {
US_LOG_PERROR("Can't unmap device buffer=%u", index);
}
}
} else { // V4L2_MEMORY_USERPTR
US_DELETE(HW(raw.data), free);
}
# undef HW
}
_RUN(n_bufs) = 0;
free(_RUN(hw_bufs));
_RUN(hw_bufs) = NULL;
}
if (_RUN(fd) >= 0) {
US_LOG_DEBUG("Closing device ...");
if (close(_RUN(fd)) < 0) {
US_LOG_PERROR("Can't close device fd=%d", _RUN(fd));
} else {
US_LOG_INFO("Device fd=%d closed", _RUN(fd));
}
_RUN(fd) = -1;
}
}
int us_device_export_to_dma(us_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;
US_LOG_DEBUG("Exporting device buffer=%u to DMA ...", index);
if (_D_XIOCTL(VIDIOC_EXPBUF, &exp) < 0) {
US_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 us_device_switch_capturing(us_device_s *dev, bool enable) {
if (enable != _RUN(capturing)) {
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
US_LOG_DEBUG("%s device capturing ...", (enable ? "Starting" : "Stopping"));
if (_D_XIOCTL((enable ? VIDIOC_STREAMON : VIDIOC_STREAMOFF), &type) < 0) {
US_LOG_PERROR("Can't %s capturing", (enable ? "start" : "stop"));
if (enable) {
return -1;
}
}
_RUN(capturing) = enable;
US_LOG_INFO("Capturing %s", (enable ? "started" : "stopped"));
}
return 0;
}
int us_device_select(us_device_s *dev, bool *has_read, bool *has_write, bool *has_error) {
int retval;
# define INIT_FD_SET(x_set) \
fd_set x_set; FD_ZERO(&x_set); FD_SET(_RUN(fd), &x_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;
US_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;
}
US_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)) {
US_LOG_ERROR("Persistent device timeout (unplugged)");
_RUN(persistent_timeout_reported) = true;
}
} else {
// Если устройство не персистентное, то таймаут является ошибкой
retval = -1;
}
}
return retval;
}
int us_device_grab_buffer(us_device_s *dev, us_hw_buffer_s **hw) {
*hw = NULL;
struct v4l2_buffer buf = {0};
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = dev->io_method;
US_LOG_DEBUG("Grabbing device buffer ...");
if (_D_XIOCTL(VIDIOC_DQBUF, &buf) < 0) {
US_LOG_PERROR("Can't grab device buffer");
return -1;
}
US_LOG_DEBUG("Grabbed new frame: buffer=%u, bytesused=%u", buf.index, buf.bytesused);
if (buf.index >= _RUN(n_bufs)) {
US_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) {
US_LOG_DEBUG("Dropped too small frame, assuming it was broken: buffer=%u, bytesused=%u",
buf.index, buf.bytesused);
US_LOG_DEBUG("Releasing device buffer=%u (broken frame) ...", buf.index);
if (_D_XIOCTL(VIDIOC_QBUF, &buf) < 0) {
US_LOG_PERROR("Can't release device buffer=%u (broken frame)", buf.index);
return -1;
}
return -2;
}
# define HW(x_next) _RUN(hw_bufs)[buf.index].x_next
if (HW(grabbed)) {
US_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) = us_get_now_monotonic();
# undef HW
*hw = &_RUN(hw_bufs[buf.index]);
return buf.index;
}
int us_device_release_buffer(us_device_s *dev, us_hw_buffer_s *hw) {
const unsigned index = hw->buf.index;
US_LOG_DEBUG("Releasing device buffer=%u ...", index);
if (_D_XIOCTL(VIDIOC_QBUF, &hw->buf) < 0) {
US_LOG_PERROR("Can't release device buffer=%u", index);
return -1;
}
hw->grabbed = false;
return 0;
}
int us_device_consume_event(us_device_s *dev) {
struct v4l2_event event;
US_LOG_DEBUG("Consuming V4L2 event ...");
if (_D_XIOCTL(VIDIOC_DQEVENT, &event) == 0) {
switch (event.type) {
case V4L2_EVENT_SOURCE_CHANGE:
US_LOG_INFO("Got V4L2_EVENT_SOURCE_CHANGE: source changed");
return -1;
case V4L2_EVENT_EOS:
US_LOG_INFO("Got V4L2_EVENT_EOS: end of stream (ignored)");
return 0;
}
} else {
US_LOG_PERROR("Got some V4L2 device event, but where is it? ");
}
return 0;
}
static int _device_open_check_cap(us_device_s *dev) {
struct v4l2_capability cap = {0};
US_LOG_DEBUG("Querying device capabilities ...");
if (_D_XIOCTL(VIDIOC_QUERYCAP, &cap) < 0) {
US_LOG_PERROR("Can't query device capabilities");
return -1;
}
if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
US_LOG_ERROR("Video capture is not supported by device");
return -1;
}
if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
US_LOG_ERROR("Device doesn't support streaming IO");
return -1;
}
int input = dev->input; // Needs a pointer to int for ioctl()
US_LOG_INFO("Using input channel: %d", input);
if (_D_XIOCTL(VIDIOC_S_INPUT, &input) < 0) {
US_LOG_ERROR("Can't set input channel");
return -1;
}
if (dev->standard != V4L2_STD_UNKNOWN) {
US_LOG_INFO("Using TV standard: %s", _standard_to_string(dev->standard));
if (_D_XIOCTL(VIDIOC_S_STD, &dev->standard) < 0) {
US_LOG_ERROR("Can't set video standard");
return -1;
}
} else {
US_LOG_DEBUG("Using TV standard: DEFAULT");
}
return 0;
}
static int _device_open_dv_timings(us_device_s *dev) {
_device_apply_resolution(dev, dev->width, dev->height);
if (dev->dv_timings) {
US_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;
US_LOG_DEBUG("Subscribing to DV-timings events ...")
if (_D_XIOCTL(VIDIOC_SUBSCRIBE_EVENT, &sub) < 0) {
US_LOG_PERROR("Can't subscribe to DV-timings events");
return -1;
}
}
return 0;
}
static int _device_apply_dv_timings(us_device_s *dev) {
struct v4l2_dv_timings dv = {0};
US_LOG_DEBUG("Calling us_xioctl(VIDIOC_QUERY_DV_TIMINGS) ...");
if (_D_XIOCTL(VIDIOC_QUERY_DV_TIMINGS, &dv) == 0) {
if (dv.type == V4L2_DV_BT_656_1120) {
// See v4l2_print_dv_timings() in the kernel
const unsigned htot = V4L2_DV_BT_FRAME_WIDTH(&dv.bt);
const unsigned vtot = V4L2_DV_BT_FRAME_HEIGHT(&dv.bt) / (dv.bt.interlaced ? 2 : 1);
const unsigned fps = ((htot * vtot) > 0 ? ((100 * (uint64_t)dv.bt.pixelclock)) / (htot * vtot) : 0);
US_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 {
US_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);
}
US_LOG_DEBUG("Calling us_xioctl(VIDIOC_S_DV_TIMINGS) ...");
if (_D_XIOCTL(VIDIOC_S_DV_TIMINGS, &dv) < 0) {
US_LOG_PERROR("Failed to set DV-timings");
return -1;
}
if (_device_apply_resolution(dev, dv.bt.width, dv.bt.height) < 0) {
return -1;
}
} else {
US_LOG_DEBUG("Calling us_xioctl(VIDIOC_QUERYSTD) ...");
if (_D_XIOCTL(VIDIOC_QUERYSTD, &dev->standard) == 0) {
US_LOG_INFO("Applying the new VIDIOC_S_STD: %s ...", _standard_to_string(dev->standard));
if (_D_XIOCTL(VIDIOC_S_STD, &dev->standard) < 0) {
US_LOG_PERROR("Can't set video standard");
return -1;
}
}
}
return 0;
}
static int _device_open_format(us_device_s *dev, bool first) {
const unsigned stride = us_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
US_LOG_DEBUG("Probing device format=%s, stride=%u, resolution=%ux%u ...",
_format_to_string_supported(dev->format), stride, _RUN(width), _RUN(height));
if (_D_XIOCTL(VIDIOC_S_FMT, &fmt) < 0) {
US_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)) {
US_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);
}
US_LOG_INFO("Using resolution: %ux%u", _RUN(width), _RUN(height));
// Check format
if (fmt.fmt.pix.pixelformat != dev->format) {
US_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) {
US_LOG_INFO("Falling back to format=%s", format_str);
} else {
char fourcc_str[8];
US_LOG_ERROR("Unsupported format=%s (fourcc)",
us_fourcc_to_string(fmt.fmt.pix.pixelformat, fourcc_str, 8));
return -1;
}
}
_RUN(format) = fmt.fmt.pix.pixelformat;
US_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(us_device_s *dev) {
_RUN(hw_fps) = 0;
struct v4l2_streamparm setfps = {0};
setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
US_LOG_DEBUG("Querying HW FPS ...");
if (_D_XIOCTL(VIDIOC_G_PARM, &setfps) < 0) {
if (errno == ENOTTY) { // Quiet message for TC358743
US_LOG_INFO("Querying HW FPS changing is not supported");
} else {
US_LOG_PERROR("Can't query HW FPS changing");
}
return;
}
if (!(setfps.parm.capture.capability & V4L2_CAP_TIMEPERFRAME)) {
US_LOG_INFO("Changing HW FPS is not supported");
return;
}
# define SETFPS_TPF(x_next) setfps.parm.capture.timeperframe.x_next
US_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 (_D_XIOCTL(VIDIOC_S_PARM, &setfps) < 0) {
US_LOG_PERROR("Can't set HW FPS");
return;
}
if (SETFPS_TPF(numerator) != 1) {
US_LOG_ERROR("Invalid HW FPS numerator: %u != 1", SETFPS_TPF(numerator));
return;
}
if (SETFPS_TPF(denominator) == 0) { // Не знаю, бывает ли так, но пускай на всякий случай
US_LOG_ERROR("Invalid HW FPS denominator: 0");
return;
}
_RUN(hw_fps) = SETFPS_TPF(denominator);
if (dev->desired_fps != _RUN(hw_fps)) {
US_LOG_INFO("Using HW FPS: %u -> %u (coerced)", dev->desired_fps, _RUN(hw_fps));
} else {
US_LOG_INFO("Using HW FPS: %u", _RUN(hw_fps));
}
# undef SETFPS_TPF
}
static void _device_open_jpeg_quality(us_device_s *dev) {
unsigned quality = 0;
if (us_is_jpeg(_RUN(format))) {
struct v4l2_jpegcompression comp = {0};
if (_D_XIOCTL(VIDIOC_G_JPEGCOMP, &comp) < 0) {
US_LOG_ERROR("Device doesn't support setting of HW encoding quality parameters");
} else {
comp.quality = dev->jpeg_quality;
if (_D_XIOCTL(VIDIOC_S_JPEGCOMP, &comp) < 0) {
US_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(us_device_s *dev) {
US_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(us_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;
US_LOG_DEBUG("Requesting %u device buffers for MMAP ...", req.count);
if (_D_XIOCTL(VIDIOC_REQBUFS, &req) < 0) {
US_LOG_PERROR("Device '%s' doesn't support MMAP method", dev->path);
return -1;
}
if (req.count < 1) {
US_LOG_ERROR("Insufficient buffer memory: %u", req.count);
return -1;
} else {
US_LOG_INFO("Requested %u device buffers, got %u", dev->n_bufs, req.count);
}
US_LOG_DEBUG("Allocating device buffers ...");
US_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);
US_LOG_DEBUG("Calling us_xioctl(VIDIOC_QUERYBUF) for device buffer=%u ...", _RUN(n_bufs));
if (_D_XIOCTL(VIDIOC_QUERYBUF, &buf) < 0) {
US_LOG_PERROR("Can't VIDIOC_QUERYBUF");
return -1;
}
# define HW(x_next) _RUN(hw_bufs)[_RUN(n_bufs)].x_next
HW(dma_fd) = -1;
US_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) {
US_LOG_PERROR("Can't map device buffer=%u", _RUN(n_bufs));
return -1;
}
assert(HW(raw.data) != NULL);
HW(raw.allocated) = buf.length;
# undef HW
}
return 0;
}
static int _device_open_io_method_userptr(us_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;
US_LOG_DEBUG("Requesting %u device buffers for USERPTR ...", req.count);
if (_D_XIOCTL(VIDIOC_REQBUFS, &req) < 0) {
US_LOG_PERROR("Device '%s' doesn't support USERPTR method", dev->path);
return -1;
}
if (req.count < 1) {
US_LOG_ERROR("Insufficient buffer memory: %u", req.count);
return -1;
} else {
US_LOG_INFO("Requested %u device buffers, got %u", dev->n_bufs, req.count);
}
US_LOG_DEBUG("Allocating device buffers ...");
US_CALLOC(_RUN(hw_bufs), req.count);
const unsigned page_size = getpagesize();
const unsigned buf_size = us_align_size(_RUN(raw_size), page_size);
for (_RUN(n_bufs) = 0; _RUN(n_bufs) < req.count; ++_RUN(n_bufs)) {
# define HW(x_next) _RUN(hw_bufs)[_RUN(n_bufs)].x_next
assert((HW(raw.data) = aligned_alloc(page_size, buf_size)) != NULL);
memset(HW(raw.data), 0, buf_size);
HW(raw.allocated) = buf_size;
# undef HW
}
return 0;
}
static int _device_open_queue_buffers(us_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;
}
US_LOG_DEBUG("Calling us_xioctl(VIDIOC_QBUF) for buffer=%u ...", index);
if (_D_XIOCTL(VIDIOC_QBUF, &buf) < 0) {
US_LOG_PERROR("Can't VIDIOC_QBUF");
return -1;
}
}
return 0;
}
static int _device_apply_resolution(us_device_s *dev, unsigned width, unsigned height) {
// Тут VIDEO_MIN_* не используются из-за странностей минимального разрешения при отсутствии сигнала
// у некоторых устройств, например TC358743
if (
width == 0 || width > US_VIDEO_MAX_WIDTH
|| height == 0 || height > US_VIDEO_MAX_HEIGHT
) {
US_LOG_ERROR("Requested forbidden resolution=%ux%u: min=1x1, max=%ux%u",
width, height, US_VIDEO_MAX_WIDTH, US_VIDEO_MAX_HEIGHT);
return -1;
}
_RUN(width) = width;
_RUN(height) = height;
return 0;
}
static void _device_apply_controls(us_device_s *dev) {
# define SET_CID_VALUE(x_cid, x_field, x_value, x_quiet) { \
struct v4l2_queryctrl m_query; \
if (_device_query_control(dev, &m_query, #x_field, x_cid, x_quiet) == 0) { \
_device_set_control(dev, &m_query, #x_field, x_cid, x_value, x_quiet); \
} \
}
# define SET_CID_DEFAULT(x_cid, x_field, x_quiet) { \
struct v4l2_queryctrl m_query; \
if (_device_query_control(dev, &m_query, #x_field, x_cid, x_quiet) == 0) { \
_device_set_control(dev, &m_query, #x_field, x_cid, m_query.default_value, x_quiet); \
} \
}
# define CONTROL_MANUAL_CID(x_cid, x_field) { \
if (dev->ctl.x_field.mode == CTL_MODE_VALUE) { \
SET_CID_VALUE(x_cid, x_field, dev->ctl.x_field.value, false); \
} else if (dev->ctl.x_field.mode == CTL_MODE_DEFAULT) { \
SET_CID_DEFAULT(x_cid, x_field, false); \
} \
}
# define CONTROL_AUTO_CID(x_cid_auto, x_cid_manual, x_field) { \
if (dev->ctl.x_field.mode == CTL_MODE_VALUE) { \
SET_CID_VALUE(x_cid_auto, x_field##_auto, 0, true); \
SET_CID_VALUE(x_cid_manual, x_field, dev->ctl.x_field.value, false); \
} else if (dev->ctl.x_field.mode == CTL_MODE_AUTO) { \
SET_CID_VALUE(x_cid_auto, x_field##_auto, 1, false); \
} else if (dev->ctl.x_field.mode == CTL_MODE_DEFAULT) { \
SET_CID_VALUE(x_cid_auto, x_field##_auto, 0, true); /* Reset inactive flag */ \
SET_CID_DEFAULT(x_cid_manual, x_field, false); \
SET_CID_DEFAULT(x_cid_auto, x_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(
us_device_s *dev, struct v4l2_queryctrl *query,
const char *name, unsigned cid, bool quiet) {
// cppcheck-suppress redundantPointerOp
US_MEMSET_ZERO(*query);
query->id = cid;
if (_D_XIOCTL(VIDIOC_QUERYCTRL, query) < 0 || query->flags & V4L2_CTRL_FLAG_DISABLED) {
if (!quiet) {
US_LOG_ERROR("Changing control %s is unsupported", name);
}
return -1;
}
return 0;
}
static void _device_set_control(
us_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) {
US_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 (_D_XIOCTL(VIDIOC_S_CTRL, &ctl) < 0) {
if (!quiet) {
US_LOG_PERROR("Can't set control %s", name);
}
} else if (!quiet) {
US_LOG_INFO("Applying control %s: %d", name, ctl.value);
}
}
static const char *_format_to_string_nullable(unsigned format) {
US_ARRAY_ITERATE(_FORMATS, 0, item, {
if (item->format == format) {
return item->name;
}
});
return NULL;
}
static const char *_format_to_string_supported(unsigned format) {
const char *const format_str = _format_to_string_nullable(format);
return (format_str == NULL ? "unsupported" : format_str);
}
static const char *_standard_to_string(v4l2_std_id standard) {
US_ARRAY_ITERATE(_STANDARDS, 0, item, {
if (item->standard == standard) {
return item->name;
}
});
return _STANDARDS[0].name;
}
static const char *_io_method_to_string_supported(enum v4l2_memory io_method) {
US_ARRAY_ITERATE(_IO_METHODS, 0, item, {
if (item->io_method == io_method) {
return item->name;
}
});
return "unsupported";
}

View File

@@ -96,7 +96,7 @@ us_workers_pool_s *us_encoder_workers_pool_init(us_encoder_s *enc, us_device_s *
us_encoder_type_e type = (_ER(cpu_forced) ? US_ENCODER_TYPE_CPU : enc->type);
unsigned quality = dev->jpeg_quality;
unsigned n_workers = us_min_u(enc->n_workers, DR(n_bufs));
unsigned n_workers = US_MIN(enc->n_workers, DR(n_bufs));
bool cpu_forced = false;
if (us_is_jpeg(DR(format)) && type != US_ENCODER_TYPE_HW) {
@@ -120,7 +120,7 @@ us_workers_pool_s *us_encoder_workers_pool_init(us_encoder_s *enc, us_device_s *
for (; _ER(n_m2ms) < n_workers; ++_ER(n_m2ms)) {
// Начинаем с нуля и доинициализируем на следующих заходах при необходимости
char name[32];
snprintf(name, 32, "JPEG-%u", _ER(n_m2ms));
US_SNPRINTF(name, 31, "JPEG-%u", _ER(n_m2ms));
if (type == US_ENCODER_TYPE_M2M_VIDEO) {
_ER(m2ms[_ER(n_m2ms)]) = us_m2m_mjpeg_encoder_init(name, enc->m2m_path, quality);
} else {
@@ -195,7 +195,7 @@ static void _worker_job_destroy(void *v_job) {
static bool _worker_run_job(us_worker_s *wr) {
us_encoder_job_s *job = (us_encoder_job_s *)wr->job;
us_encoder_s *enc = job->enc; // Just for _ER()
us_frame_s *src = &job->hw->raw;
const us_frame_s *src = &job->hw->raw;
us_frame_s *dest = job->dest;
assert(_ER(type) != US_ENCODER_TYPE_UNKNOWN);

View File

@@ -35,8 +35,8 @@
#include "../libs/threading.h"
#include "../libs/logging.h"
#include "../libs/frame.h"
#include "../libs/device.h"
#include "device.h"
#include "workers.h"
#include "m2m.h"

View File

@@ -37,10 +37,10 @@ typedef struct {
static void _jpeg_set_dest_frame(j_compress_ptr jpeg, us_frame_s *frame);
static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
static void _jpeg_write_scanlines_yuv(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
static void _jpeg_write_scanlines_rgb24(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
static void _jpeg_write_scanlines_bgr24(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
static void _jpeg_init_destination(j_compress_ptr jpeg);
static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg);
@@ -63,27 +63,24 @@ void us_cpu_encoder_compress(const us_frame_s *src, us_frame_s *dest, unsigned q
jpeg.image_width = src->width;
jpeg.image_height = src->height;
jpeg.input_components = 3;
jpeg.in_color_space = JCS_RGB;
jpeg.in_color_space = ((src->format == V4L2_PIX_FMT_YUYV || src->format == V4L2_PIX_FMT_UYVY) ? JCS_YCbCr : JCS_RGB);
jpeg_set_defaults(&jpeg);
jpeg_set_quality(&jpeg, quality, TRUE);
jpeg_start_compress(&jpeg, TRUE);
# define WRITE_SCANLINES(x_format, x_func) \
case x_format: { x_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");
case V4L2_PIX_FMT_YUYV:
case V4L2_PIX_FMT_YVYU:
case V4L2_PIX_FMT_UYVY: _jpeg_write_scanlines_yuv(&jpeg, src); break;
case V4L2_PIX_FMT_RGB565: _jpeg_write_scanlines_rgb565(&jpeg, src); break;
case V4L2_PIX_FMT_RGB24: _jpeg_write_scanlines_rgb24(&jpeg, src); break;
case V4L2_PIX_FMT_BGR24: _jpeg_write_scanlines_bgr24(&jpeg, src); break;
default: assert(0 && "Unsupported input format for CPU encoder"); return;
}
# undef WRITE_SCANLINES
jpeg_finish_compress(&jpeg);
jpeg_destroy_compress(&jpeg);
@@ -106,39 +103,43 @@ static void _jpeg_set_dest_frame(j_compress_ptr jpeg, us_frame_s *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 us_frame_s *frame) {
static void _jpeg_write_scanlines_yuv(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) {
uint8_t *line_buf;
US_CALLOC(line_buf, frame->width * 3);
const unsigned padding = us_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) {
const int y = (!z ? data[0] << 8 : data[2] << 8);
const int u = data[1] - 128;
const int v = data[3] - 128;
const int r = YUV_R(y, u, v);
const int g = YUV_G(y, u, v);
const 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;
// See also: https://www.kernel.org/doc/html/v4.8/media/uapi/v4l/pixfmt-uyvy.html
const bool is_odd_pixel = x & 1;
uint8_t y, u, v;
if (frame->format == V4L2_PIX_FMT_YUYV) {
y = data[is_odd_pixel ? 2 : 0];
u = data[1];
v = data[3];
} else if (frame->format == V4L2_PIX_FMT_YVYU) {
y = data[is_odd_pixel ? 2 : 0];
u = data[3];
v = data[1];
} else if (frame->format == V4L2_PIX_FMT_UYVY) {
y = data[is_odd_pixel ? 3 : 1];
u = data[0];
v = data[2];
} else {
assert(0 && "Unsupported pixel format");
return; // Makes linter happy
}
ptr[0] = y;
ptr[1] = u;
ptr[2] = v;
ptr += 3;
data += (is_odd_pixel ? 4 : 0);
}
data += padding;
@@ -149,49 +150,6 @@ static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg, const
free(line_buf);
}
static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) {
uint8_t *line_buf;
US_CALLOC(line_buf, frame->width * 3);
const unsigned padding = us_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) {
const int y = (!z ? data[1] << 8 : data[3] << 8);
const int u = data[0] - 128;
const int v = data[2] - 128;
const int r = YUV_R(y, u, v);
const int g = YUV_G(y, u, v);
const 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 us_frame_s *frame) {
uint8_t *line_buf;
US_CALLOC(line_buf, frame->width * 3);
@@ -205,9 +163,10 @@ static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, cons
for (unsigned x = 0; x < frame->width; ++x) {
const 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
ptr[0] = data[1] & 248; // Red
ptr[1] = (uint8_t)((two_byte & 2016) >> 3); // Green
ptr[2] = (data[0] & 31) * 8; // Blue
ptr += 3;
data += 2;
}
@@ -232,6 +191,33 @@ static void _jpeg_write_scanlines_rgb24(struct jpeg_compress_struct *jpeg, const
}
}
static void _jpeg_write_scanlines_bgr24(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) {
uint8_t *line_buf;
US_CALLOC(line_buf, frame->width * 3);
const unsigned padding = us_frame_get_padding(frame);
uint8_t *data = frame->data;
while (jpeg->next_scanline < frame->height) {
uint8_t *ptr = line_buf;
// swap B and R values
for (unsigned x = 0; x < frame->width * 3; x += 3) {
ptr[0] = data[x + 2];
ptr[1] = data[x + 1];
ptr[2] = data[x];
ptr += 3;
}
JSAMPROW scanlines[1] = {line_buf};
jpeg_write_scanlines(jpeg, scanlines, 1);
data += (frame->width * 3) + padding;
}
free(line_buf);
}
#define JPEG_OUTPUT_BUFFER_SIZE ((size_t)4096)
static void _jpeg_init_destination(j_compress_ptr jpeg) {

View File

@@ -34,24 +34,26 @@ us_gpio_s us_g_gpio = {
.line = NULL, \
.state = false \
}
.prog_running = MAKE_OUTPUT("prog-running"),
.stream_online = MAKE_OUTPUT("stream-online"),
.has_http_clients = MAKE_OUTPUT("has-http-clients"),
# undef MAKE_OUTPUT
// mutex uninitialized
.chip = NULL
# ifndef HAVE_GPIOD2
.chip = NULL,
# endif
.initialized = false,
};
static void _gpio_output_init(us_gpio_output_s *output);
static void _gpio_output_init(us_gpio_output_s *output, struct gpiod_chip *chip);
static void _gpio_output_destroy(us_gpio_output_s *output);
void us_gpio_init(void) {
# ifndef HAVE_GPIOD2
assert(us_g_gpio.chip == NULL);
# endif
if (
us_g_gpio.prog_running.pin >= 0
|| us_g_gpio.stream_online.pin >= 0
@@ -59,10 +61,17 @@ void us_gpio_init(void) {
) {
US_MUTEX_INIT(us_g_gpio.mutex);
US_LOG_INFO("GPIO: Using chip device: %s", us_g_gpio.path);
if ((us_g_gpio.chip = gpiod_chip_open(us_g_gpio.path)) != NULL) {
_gpio_output_init(&us_g_gpio.prog_running);
_gpio_output_init(&us_g_gpio.stream_online);
_gpio_output_init(&us_g_gpio.has_http_clients);
struct gpiod_chip *chip;
if ((chip = gpiod_chip_open(us_g_gpio.path)) != NULL) {
_gpio_output_init(&us_g_gpio.prog_running, chip);
_gpio_output_init(&us_g_gpio.stream_online, chip);
_gpio_output_init(&us_g_gpio.has_http_clients, chip);
# ifdef HAVE_GPIOD2
gpiod_chip_close(chip);
# else
us_g_gpio.chip = chip;
# endif
us_g_gpio.initialized = true;
} else {
US_LOG_PERROR("GPIO: Can't initialize chip device %s", us_g_gpio.path);
}
@@ -73,23 +82,32 @@ void us_gpio_destroy(void) {
_gpio_output_destroy(&us_g_gpio.prog_running);
_gpio_output_destroy(&us_g_gpio.stream_online);
_gpio_output_destroy(&us_g_gpio.has_http_clients);
if (us_g_gpio.chip != NULL) {
if (us_g_gpio.initialized) {
# ifndef HAVE_GPIOD2
gpiod_chip_close(us_g_gpio.chip);
us_g_gpio.chip = NULL;
# endif
US_MUTEX_DESTROY(us_g_gpio.mutex);
us_g_gpio.initialized = false;
}
}
int us_gpio_inner_set(us_gpio_output_s *output, bool state) {
int retval = 0;
# ifndef HAVE_GPIOD2
assert(us_g_gpio.chip != NULL);
# endif
assert(output->line != NULL);
assert(output->state != state); // Must be checked in macro for the performance
US_MUTEX_LOCK(us_g_gpio.mutex);
if (gpiod_line_set_value(output->line, (int)state) < 0) { \
US_LOG_PERROR("GPIO: Can't write value %d to line %s (will be disabled)", state, output->consumer); \
# ifdef HAVE_GPIOD2
if (gpiod_line_request_set_value(output->line, output->pin, state) < 0) {
# else
if (gpiod_line_set_value(output->line, (int)state) < 0) {
# endif
US_LOG_PERROR("GPIO: Can't write value %d to line %s", state, output->consumer); \
_gpio_output_destroy(output);
retval = -1;
}
@@ -98,14 +116,42 @@ int us_gpio_inner_set(us_gpio_output_s *output, bool state) {
return retval;
}
static void _gpio_output_init(us_gpio_output_s *output) {
assert(us_g_gpio.chip != NULL);
static void _gpio_output_init(us_gpio_output_s *output, struct gpiod_chip *chip) {
assert(output->line == NULL);
US_ASPRINTF(output->consumer, "%s::%s", us_g_gpio.consumer_prefix, output->role);
if (output->pin >= 0) {
if ((output->line = gpiod_chip_get_line(us_g_gpio.chip, output->pin)) != NULL) {
# ifdef HAVE_GPIOD2
struct gpiod_line_settings *line_settings;
assert(line_settings = gpiod_line_settings_new());
assert(!gpiod_line_settings_set_direction(line_settings, GPIOD_LINE_DIRECTION_OUTPUT));
assert(!gpiod_line_settings_set_output_value(line_settings, false));
struct gpiod_line_config *line_config;
assert(line_config = gpiod_line_config_new());
const unsigned offset = output->pin;
assert(!gpiod_line_config_add_line_settings(line_config, &offset, 1, line_settings));
struct gpiod_request_config *request_config;
assert(request_config = gpiod_request_config_new());
gpiod_request_config_set_consumer(request_config, output->consumer);
if ((output->line = gpiod_chip_request_lines(chip, request_config, line_config)) == NULL) {
US_LOG_PERROR("GPIO: Can't request pin=%d as %s", output->pin, output->consumer);
}
gpiod_request_config_free(request_config);
gpiod_line_config_free(line_config);
gpiod_line_settings_free(line_settings);
if (output->line == NULL) {
_gpio_output_destroy(output);
}
# else
if ((output->line = gpiod_chip_get_line(chip, output->pin)) != NULL) {
if (gpiod_line_request_output(output->line, output->consumer, 0) < 0) {
US_LOG_PERROR("GPIO: Can't request pin=%d as %s", output->pin, output->consumer);
_gpio_output_destroy(output);
@@ -113,12 +159,17 @@ static void _gpio_output_init(us_gpio_output_s *output) {
} else {
US_LOG_PERROR("GPIO: Can't get pin=%d as %s", output->pin, output->consumer);
}
# endif
}
}
static void _gpio_output_destroy(us_gpio_output_s *output) {
if (output->line != NULL) {
# ifdef HAVE_GPIOD2
gpiod_line_request_release(output->line);
# else
gpiod_line_release(output->line);
# endif
output->line = NULL;
}
if (output->consumer != NULL) {

View File

@@ -36,11 +36,15 @@
typedef struct {
int pin;
const char *role;
char *consumer;
struct gpiod_line *line;
bool state;
int pin;
const char *role;
char *consumer;
# ifdef HAVE_GPIOD2
struct gpiod_line_request *line;
# else
struct gpiod_line *line;
# endif
bool state;
} us_gpio_output_s;
typedef struct {
@@ -52,7 +56,11 @@ typedef struct {
us_gpio_output_s has_http_clients;
pthread_mutex_t mutex;
# ifndef HAVE_GPIOD2
struct gpiod_chip *chip;
# endif
bool initialized;
} us_gpio_s;

View File

@@ -31,10 +31,9 @@ char *us_bufferevent_format_reason(short what) {
char *const perror_str = us_errno_to_string(EVUTIL_SOCKET_ERROR());
bool first = true;
strcat(reason, perror_str);
strncat(reason, perror_str, 1023);
free(perror_str);
strcat(reason, " (");
# define FILL_REASON(x_bev, x_name) { \
if (what & x_bev) { \
if (first) { \
@@ -51,7 +50,6 @@ char *us_bufferevent_format_reason(short what) {
FILL_REASON(BEV_EVENT_ERROR, "error");
FILL_REASON(BEV_EVENT_TIMEOUT, "timeout");
FILL_REASON(BEV_EVENT_EOF, "eof"); // cppcheck-suppress unreadVariable
# undef FILL_REASON
strcat(reason, ")");

View File

@@ -41,7 +41,7 @@ static void _http_request_watcher(int fd, short event, void *v_server);
static void _http_refresher(int fd, short event, void *v_server);
static void _http_queue_send_stream(us_server_s *server, bool stream_updated, bool frame_updated);
static bool _expose_new_frame(us_server_s *server);
static bool _expose_frame(us_server_s *server, const us_frame_s *frame);
static const char *_http_get_header(struct evhttp_request *request, const char *key);
static char *_http_get_client_hostport(struct evhttp_request *request);
@@ -51,20 +51,15 @@ static char *_http_get_client_hostport(struct evhttp_request *request);
#define _A_EVBUFFER_ADD(x_buf, x_data, x_size) assert(!evbuffer_add(x_buf, x_data, x_size))
#define _A_EVBUFFER_ADD_PRINTF(x_buf, x_fmt, ...) assert(evbuffer_add_printf(x_buf, x_fmt, ##__VA_ARGS__) >= 0)
#define _RUN(x_next) server->run->x_next
#define _STREAM(x_next) _RUN(stream->x_next)
#define _VID(x_next) _STREAM(run->video->x_next)
#define _EX(x_next) _RUN(exposed->x_next)
us_server_s *us_server_init(us_stream_s *stream) {
us_exposed_s *exposed;
us_server_exposed_s *exposed;
US_CALLOC(exposed, 1);
exposed->frame = us_frame_init();
us_server_runtime_s *run;
US_CALLOC(run, 1);
run->stream = stream;
run->ext_fd = -1;
run->exposed = exposed;
us_server_s *server;
@@ -78,6 +73,7 @@ us_server_s *us_server_init(us_stream_s *stream) {
server->allow_origin = "";
server->instance_id = "";
server->timeout = 10;
server->stream = stream;
server->run = run;
assert(!evthread_use_pthreads());
@@ -88,78 +84,82 @@ us_server_s *us_server_init(us_stream_s *stream) {
}
void us_server_destroy(us_server_s *server) {
if (_RUN(refresher) != NULL) {
event_del(_RUN(refresher));
event_free(_RUN(refresher));
us_server_runtime_s *const run = server->run;
if (run->refresher != NULL) {
event_del(run->refresher);
event_free(run->refresher);
}
if (_RUN(request_watcher) != NULL) {
event_del(_RUN(request_watcher));
event_free(_RUN(request_watcher));
if (run->request_watcher != NULL) {
event_del(run->request_watcher);
event_free(run->request_watcher);
}
evhttp_free(_RUN(http));
if (_RUN(ext_fd) >= 0) {
close(_RUN(ext_fd));
}
event_base_free(_RUN(base));
evhttp_free(run->http);
US_CLOSE_FD(run->ext_fd);
event_base_free(run->base);
# if LIBEVENT_VERSION_NUMBER >= 0x02010100
libevent_global_shutdown();
# endif
US_LIST_ITERATE(_RUN(stream_clients), client, {
US_LIST_ITERATE(run->stream_clients, client, { // cppcheck-suppress constStatement
free(client->key);
free(client->hostport);
free(client);
});
US_DELETE(_RUN(auth_token), free);
US_DELETE(run->auth_token, free);
us_frame_destroy(_EX(frame));
free(_RUN(exposed));
us_frame_destroy(run->exposed->frame);
free(run->exposed);
free(server->run);
free(server);
}
int us_server_listen(us_server_s *server) {
us_server_runtime_s *const run = server->run;
us_server_exposed_s *const ex = run->exposed;
us_stream_s *const stream = server->stream;
{
if (server->static_path[0] != '\0') {
US_LOG_INFO("Enabling HTTP file server: %s", server->static_path);
evhttp_set_gencb(_RUN(http), _http_callback_static, (void *)server);
evhttp_set_gencb(run->http, _http_callback_static, (void *)server);
} else {
assert(!evhttp_set_cb(_RUN(http), "/", _http_callback_root, (void *)server));
assert(!evhttp_set_cb(_RUN(http), "/favicon.ico", _http_callback_favicon, (void *)server));
assert(!evhttp_set_cb(run->http, "/", _http_callback_root, (void *)server));
assert(!evhttp_set_cb(run->http, "/favicon.ico", _http_callback_favicon, (void *)server));
}
assert(!evhttp_set_cb(_RUN(http), "/state", _http_callback_state, (void *)server));
assert(!evhttp_set_cb(_RUN(http), "/snapshot", _http_callback_snapshot, (void *)server));
assert(!evhttp_set_cb(_RUN(http), "/stream", _http_callback_stream, (void *)server));
assert(!evhttp_set_cb(run->http, "/state", _http_callback_state, (void *)server));
assert(!evhttp_set_cb(run->http, "/snapshot", _http_callback_snapshot, (void *)server));
assert(!evhttp_set_cb(run->http, "/stream", _http_callback_stream, (void *)server));
}
us_frame_copy(_STREAM(blank), _EX(frame));
_EX(notify_last_width) = _EX(frame->width);
_EX(notify_last_height) = _EX(frame->height);
us_frame_copy(stream->run->blank->jpeg, ex->frame);
ex->notify_last_width = ex->frame->width;
ex->notify_last_height = ex->frame->height;
if (server->exit_on_no_clients > 0) {
_RUN(last_request_ts) = us_get_now_monotonic();
run->last_request_ts = us_get_now_monotonic();
struct timeval interval = {0};
interval.tv_usec = 100000;
assert((_RUN(request_watcher) = event_new(_RUN(base), -1, EV_PERSIST, _http_request_watcher, server)) != NULL);
assert(!event_add(_RUN(request_watcher), &interval));
assert((run->request_watcher = event_new(run->base, -1, EV_PERSIST, _http_request_watcher, server)) != NULL);
assert(!event_add(run->request_watcher, &interval));
}
{
struct timeval interval = {0};
if (_STREAM(dev->desired_fps) > 0) {
interval.tv_usec = 1000000 / (_STREAM(dev->desired_fps) * 2);
if (stream->dev->desired_fps > 0) {
interval.tv_usec = 1000000 / (stream->dev->desired_fps * 2);
} else {
interval.tv_usec = 16000; // ~60fps
}
assert((_RUN(refresher) = event_new(_RUN(base), -1, EV_PERSIST, _http_refresher, server)) != NULL);
assert(!event_add(_RUN(refresher), &interval));
assert((run->refresher = event_new(run->base, -1, EV_PERSIST, _http_refresher, server)) != NULL);
assert(!event_add(run->refresher, &interval));
}
evhttp_set_timeout(_RUN(http), server->timeout);
evhttp_set_timeout(run->http, server->timeout);
if (server->user[0] != '\0') {
char *encoded_token = NULL;
@@ -169,7 +169,7 @@ int us_server_listen(us_server_s *server) {
us_base64_encode((uint8_t *)raw_token, strlen(raw_token), &encoded_token, NULL);
free(raw_token);
US_ASPRINTF(_RUN(auth_token), "Basic %s", encoded_token);
US_ASPRINTF(run->auth_token, "Basic %s", encoded_token);
free(encoded_token);
US_LOG_INFO("Using HTTP basic auth");
@@ -177,8 +177,8 @@ int us_server_listen(us_server_s *server) {
if (server->unix_path[0] != '\0') {
US_LOG_DEBUG("Binding HTTP to UNIX socket '%s' ...", server->unix_path);
if ((_RUN(ext_fd) = us_evhttp_bind_unix(
_RUN(http),
if ((run->ext_fd = us_evhttp_bind_unix(
run->http,
server->unix_path,
server->unix_rm,
server->unix_mode)) < 0
@@ -190,7 +190,7 @@ int us_server_listen(us_server_s *server) {
# ifdef WITH_SYSTEMD
} else if (server->systemd) {
US_LOG_DEBUG("Binding HTTP to systemd socket ...");
if ((_RUN(ext_fd) = us_evhttp_bind_systemd(_RUN(http))) < 0) {
if ((run->ext_fd = us_evhttp_bind_systemd(run->http)) < 0) {
return -1;
}
US_LOG_INFO("Listening systemd socket ...");
@@ -198,7 +198,7 @@ int us_server_listen(us_server_s *server) {
} else {
US_LOG_DEBUG("Binding HTTP to [%s]:%u ...", server->host, server->port);
if (evhttp_bind_socket(_RUN(http), server->host, server->port) < 0) {
if (evhttp_bind_socket(run->http, server->host, server->port) < 0) {
US_LOG_PERROR("Can't bind HTTP on [%s]:%u", server->host, server->port)
return -1;
}
@@ -210,18 +210,20 @@ int us_server_listen(us_server_s *server) {
void us_server_loop(us_server_s *server) {
US_LOG_INFO("Starting HTTP eventloop ...");
event_base_dispatch(_RUN(base));
event_base_dispatch(server->run->base);
US_LOG_INFO("HTTP eventloop stopped");
}
void us_server_loop_break(us_server_s *server) {
event_base_loopbreak(_RUN(base));
event_base_loopbreak(server->run->base);
}
#define ADD_HEADER(x_key, x_value) assert(!evhttp_add_header(evhttp_request_get_output_headers(request), x_key, x_value))
static int _http_preprocess_request(struct evhttp_request *request, us_server_s *server) {
_RUN(last_request_ts) = us_get_now_monotonic();
us_server_runtime_s *const run = server->run;
run->last_request_ts = us_get_now_monotonic();
if (server->allow_origin[0] != '\0') {
const char *const cors_headers = _http_get_header(request, "Access-Control-Request-Headers");
@@ -242,10 +244,10 @@ static int _http_preprocess_request(struct evhttp_request *request, us_server_s
return -1;
}
if (_RUN(auth_token) != NULL) {
if (run->auth_token != NULL) {
const char *const token = _http_get_header(request, "Authorization");
if (token == NULL || strcmp(token, _RUN(auth_token)) != 0) {
if (token == NULL || strcmp(token, run->auth_token) != 0) {
ADD_HEADER("WWW-Authenticate", "Basic realm=\"Restricted area\"");
evhttp_send_reply(request, 401, "Unauthorized", NULL);
return -1;
@@ -269,24 +271,22 @@ static int _http_preprocess_request(struct evhttp_request *request, us_server_s
static int _http_check_run_compat_action(struct evhttp_request *request, void *v_server) {
// MJPG-Streamer compatibility layer
struct evkeyvalq params;
int error = 0;
int retval = -1;
struct evkeyvalq params;
evhttp_parse_query(evhttp_request_get_uri(request), &params);
const char *const action = evhttp_find_header(&params, "action");
if (action && !strcmp(action, "snapshot")) {
_http_callback_snapshot(request, v_server);
goto ok;
retval = 0;
} else if (action && !strcmp(action, "stream")) {
_http_callback_stream(request, v_server);
goto ok;
retval = 0;
}
error = -1;
ok:
evhttp_clear_headers(&params);
return error;
evhttp_clear_headers(&params);
return retval;
}
#define COMPAT_REQUEST { \
@@ -338,14 +338,12 @@ static void _http_callback_static(struct evhttp_request *request, void *v_server
{
const char *uri_path;
if ((uri = evhttp_uri_parse(evhttp_request_get_uri(request))) == NULL) {
goto bad_request;
}
if ((uri_path = (char *)evhttp_uri_get_path(uri)) == NULL) {
uri_path = "/";
}
if ((decoded_path = evhttp_uridecode(uri_path, 0, NULL)) == NULL) {
goto bad_request;
}
@@ -383,34 +381,35 @@ static void _http_callback_static(struct evhttp_request *request, void *v_server
goto cleanup;
}
bad_request:
evhttp_send_error(request, HTTP_BADREQUEST, NULL);
goto cleanup;
bad_request:
evhttp_send_error(request, HTTP_BADREQUEST, NULL);
goto cleanup;
not_found:
evhttp_send_error(request, HTTP_NOTFOUND, NULL);
goto cleanup;
not_found:
evhttp_send_error(request, HTTP_NOTFOUND, NULL);
goto cleanup;
cleanup:
if (fd >= 0) {
close(fd);
}
US_DELETE(static_path, free);
US_DELETE(buf, evbuffer_free);
US_DELETE(decoded_path, free);
US_DELETE(uri, evhttp_uri_free);
cleanup:
US_CLOSE_FD(fd); // cppcheck-suppress unreadVariable
US_DELETE(static_path, free);
US_DELETE(buf, evbuffer_free);
US_DELETE(decoded_path, free);
US_DELETE(uri, evhttp_uri_free);
}
#undef COMPAT_REQUEST
static void _http_callback_state(struct evhttp_request *request, void *v_server) {
us_server_s *const server = (us_server_s *)v_server;
us_server_runtime_s *const run = server->run;
us_server_exposed_s *const ex = run->exposed;
us_stream_s *const stream = server->stream;
PREPROCESS_REQUEST;
us_encoder_type_e enc_type;
unsigned enc_quality;
us_encoder_get_runtime_params(_STREAM(enc), &enc_type, &enc_quality);
us_encoder_get_runtime_params(stream->enc, &enc_type, &enc_quality);
struct evbuffer *buf;
_A_EVBUFFER_NEW(buf);
@@ -424,28 +423,28 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
enc_quality
);
if (_STREAM(run->h264) != NULL) {
if (stream->run->h264 != NULL) {
_A_EVBUFFER_ADD_PRINTF(buf,
" \"h264\": {\"bitrate\": %u, \"gop\": %u, \"online\": %s},",
_STREAM(h264_bitrate),
_STREAM(h264_gop),
us_bool_to_string(atomic_load(&_STREAM(run->h264->online)))
stream->h264_bitrate,
stream->h264_gop,
us_bool_to_string(atomic_load(&stream->run->h264->online))
);
}
if (_STREAM(sink) != NULL || _STREAM(h264_sink) != NULL) {
if (stream->sink != NULL || stream->h264_sink != NULL) {
_A_EVBUFFER_ADD_PRINTF(buf, " \"sinks\": {");
if (_STREAM(sink) != NULL) {
if (stream->sink != NULL) {
_A_EVBUFFER_ADD_PRINTF(buf,
"\"jpeg\": {\"has_clients\": %s}",
us_bool_to_string(atomic_load(&_STREAM(sink->has_clients)))
us_bool_to_string(atomic_load(&stream->sink->has_clients))
);
}
if (_STREAM(h264_sink) != NULL) {
if (stream->h264_sink != NULL) {
_A_EVBUFFER_ADD_PRINTF(buf,
"%s\"h264\": {\"has_clients\": %s}",
(_STREAM(sink) ? ", " : ""),
us_bool_to_string(atomic_load(&_STREAM(h264_sink->has_clients)))
(stream->sink ? ", " : ""),
us_bool_to_string(atomic_load(&stream->h264_sink->has_clients))
);
}
_A_EVBUFFER_ADD_PRINTF(buf, "},");
@@ -455,16 +454,16 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
" \"source\": {\"resolution\": {\"width\": %u, \"height\": %u},"
" \"online\": %s, \"desired_fps\": %u, \"captured_fps\": %u},"
" \"stream\": {\"queued_fps\": %u, \"clients\": %u, \"clients_stat\": {",
(server->fake_width ? server->fake_width : _EX(frame->width)),
(server->fake_height ? server->fake_height : _EX(frame->height)),
us_bool_to_string(_EX(frame->online)),
_STREAM(dev->desired_fps),
_EX(captured_fps),
_EX(queued_fps),
_RUN(stream_clients_count)
(server->fake_width ? server->fake_width : ex->frame->width),
(server->fake_height ? server->fake_height : ex->frame->height),
us_bool_to_string(ex->frame->online),
stream->dev->desired_fps,
atomic_load(&stream->run->captured_fps),
ex->queued_fps,
run->stream_clients_count
);
US_LIST_ITERATE(_RUN(stream_clients), client, {
US_LIST_ITERATE(run->stream_clients, client, { // cppcheck-suppress constStatement
_A_EVBUFFER_ADD_PRINTF(buf,
"\"%" PRIx64 "\": {\"fps\": %u, \"extra_headers\": %s, \"advance_headers\": %s,"
" \"dual_final_frames\": %s, \"zero_data\": %s, \"key\": \"%s\"}%s",
@@ -488,12 +487,13 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
static void _http_callback_snapshot(struct evhttp_request *request, void *v_server) {
us_server_s *const server = (us_server_s *)v_server;
us_server_exposed_s *const ex = server->run->exposed;
PREPROCESS_REQUEST;
struct evbuffer *buf;
_A_EVBUFFER_NEW(buf);
_A_EVBUFFER_ADD(buf, (const void *)_EX(frame->data), _EX(frame->used));
_A_EVBUFFER_ADD(buf, (const void*)ex->frame->data, ex->frame->used);
ADD_HEADER("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate, pre-check=0, post-check=0, max-age=0");
ADD_HEADER("Pragma", "no-cache");
@@ -502,27 +502,27 @@ static void _http_callback_snapshot(struct evhttp_request *request, void *v_serv
char header_buf[256];
# define ADD_TIME_HEADER(x_key, x_value) { \
snprintf(header_buf, 255, "%.06Lf", x_value); \
US_SNPRINTF(header_buf, 255, "%.06Lf", x_value); \
ADD_HEADER(x_key, header_buf); \
}
# define ADD_UNSIGNED_HEADER(x_key, x_value) { \
snprintf(header_buf, 255, "%u", x_value); \
US_SNPRINTF(header_buf, 255, "%u", x_value); \
ADD_HEADER(x_key, header_buf); \
}
ADD_TIME_HEADER("X-Timestamp", us_get_now_real());
ADD_HEADER("X-UStreamer-Online", us_bool_to_string(_EX(frame->online)));
ADD_UNSIGNED_HEADER("X-UStreamer-Dropped", _EX(dropped));
ADD_UNSIGNED_HEADER("X-UStreamer-Width", _EX(frame->width));
ADD_UNSIGNED_HEADER("X-UStreamer-Height", _EX(frame->height));
ADD_TIME_HEADER("X-UStreamer-Grab-Timestamp", _EX(frame->grab_ts));
ADD_TIME_HEADER("X-UStreamer-Encode-Begin-Timestamp", _EX(frame->encode_begin_ts));
ADD_TIME_HEADER("X-UStreamer-Encode-End-Timestamp", _EX(frame->encode_end_ts));
ADD_TIME_HEADER("X-UStreamer-Expose-Begin-Timestamp", _EX(expose_begin_ts));
ADD_TIME_HEADER("X-UStreamer-Expose-Cmp-Timestamp", _EX(expose_cmp_ts));
ADD_TIME_HEADER("X-UStreamer-Expose-End-Timestamp", _EX(expose_end_ts));
ADD_HEADER("X-UStreamer-Online", us_bool_to_string(ex->frame->online));
ADD_UNSIGNED_HEADER("X-UStreamer-Dropped", ex->dropped);
ADD_UNSIGNED_HEADER("X-UStreamer-Width", ex->frame->width);
ADD_UNSIGNED_HEADER("X-UStreamer-Height", ex->frame->height);
ADD_TIME_HEADER("X-UStreamer-Grab-Timestamp", ex->frame->grab_ts);
ADD_TIME_HEADER("X-UStreamer-Encode-Begin-Timestamp", ex->frame->encode_begin_ts);
ADD_TIME_HEADER("X-UStreamer-Encode-End-Timestamp", ex->frame->encode_end_ts);
ADD_TIME_HEADER("X-UStreamer-Expose-Begin-Timestamp", ex->expose_begin_ts);
ADD_TIME_HEADER("X-UStreamer-Expose-Cmp-Timestamp", ex->expose_cmp_ts);
ADD_TIME_HEADER("X-UStreamer-Expose-End-Timestamp", ex->expose_end_ts);
ADD_TIME_HEADER("X-UStreamer-Send-Timestamp", us_get_now_monotonic());
# undef ADD_UNSUGNED_HEADER
@@ -544,6 +544,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
// https://github.com/libevent/libevent/blob/29cc8386a2f7911eaa9336692a2c5544d8b4734f/http.c#L1458
us_server_s *const server = (us_server_s *)v_server;
us_server_runtime_s *const run = server->run;
PREPROCESS_REQUEST;
@@ -570,20 +571,20 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
client->hostport = _http_get_client_hostport(request);
client->id = us_get_now_id();
US_LIST_APPEND_C(_RUN(stream_clients), client, _RUN(stream_clients_count));
US_LIST_APPEND_C(run->stream_clients, client, run->stream_clients_count);
if (_RUN(stream_clients_count) == 1) {
atomic_store(&_VID(has_clients), true);
if (run->stream_clients_count == 1) {
atomic_store(&server->stream->run->http_has_clients, true);
# ifdef WITH_GPIO
us_gpio_set_has_http_clients(true);
# endif
}
US_LOG_INFO("HTTP: NEW client (now=%u): %s, id=%" PRIx64,
_RUN(stream_clients_count), client->hostport, client->id);
run->stream_clients_count, client->hostport, client->id);
struct bufferevent *const buf_event = evhttp_connection_get_bufferevent(conn);
if (server->tcp_nodelay && !_RUN(ext_fd)) {
if (server->tcp_nodelay && run->ext_fd >= 0) {
US_LOG_DEBUG("HTTP: Setting up TCP_NODELAY to the client %s ...", client->hostport);
const evutil_socket_t fd = bufferevent_getfd(buf_event);
assert(fd >= 0);
@@ -602,10 +603,9 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
#undef PREPROCESS_REQUEST
static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_client) {
# define BOUNDARY "boundarydonotcross"
us_stream_client_s *const client = (us_stream_client_s *)v_client;
us_server_s *const server = client->server;
us_server_exposed_s *const ex = server->run->exposed;
const long double now = us_get_now_monotonic();
const long long now_second = us_floor_ms(now);
@@ -639,6 +639,8 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
// Кроме того, advance_headers форсит отключение заголовков X-UStreamer-*
// по тем же причинам, по которым у нас нет Content-Length.
# define BOUNDARY "boundarydonotcross"
# define ADD_ADVANCE_HEADERS \
_A_EVBUFFER_ADD_PRINTF(buf, \
"Content-Type: image/jpeg" RN "X-Timestamp: %.06Lf" RN RN, us_get_now_real())
@@ -667,10 +669,12 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
"Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate, pre-check=0, post-check=0, max-age=0" RN
"Pragma: no-cache" RN
"Expires: Mon, 3 Jan 2000 12:34:56 GMT" RN
"Set-Cookie: stream_client=%s/%" PRIx64 "; path=/; max-age=30" RN
"Set-Cookie: stream_client%s%s=%s/%" PRIx64 "; path=/; max-age=30" RN
"Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY RN
RN
"--" BOUNDARY RN,
(server->instance_id[0] == '\0' ? "" : "_"),
server->instance_id,
(client->key != NULL ? client->key : "0"),
client->id
);
@@ -689,7 +693,7 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
"Content-Length: %zu" RN
"X-Timestamp: %.06Lf" RN
"%s",
(!client->zero_data ? _EX(frame->used) : 0),
(!client->zero_data ? ex->frame->used : 0),
us_get_now_real(),
(client->extra_headers ? "" : RN)
);
@@ -709,25 +713,25 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
"X-UStreamer-Send-Time: %.06Lf" RN
"X-UStreamer-Latency: %.06Lf" RN
RN,
us_bool_to_string(_EX(frame->online)),
_EX(dropped),
_EX(frame->width),
_EX(frame->height),
us_bool_to_string(ex->frame->online),
ex->dropped,
ex->frame->width,
ex->frame->height,
client->fps,
_EX(frame->grab_ts),
_EX(frame->encode_begin_ts),
_EX(frame->encode_end_ts),
_EX(expose_begin_ts),
_EX(expose_cmp_ts),
_EX(expose_end_ts),
ex->frame->grab_ts,
ex->frame->encode_begin_ts,
ex->frame->encode_end_ts,
ex->expose_begin_ts,
ex->expose_cmp_ts,
ex->expose_end_ts,
now,
now - _EX(frame->grab_ts)
now - ex->frame->grab_ts
);
}
}
if (!client->zero_data) {
_A_EVBUFFER_ADD(buf, (void *)_EX(frame->data), _EX(frame->used));
_A_EVBUFFER_ADD(buf, (void*)ex->frame->data, ex->frame->used);
}
_A_EVBUFFER_ADD_PRINTF(buf, RN "--" BOUNDARY RN);
@@ -745,14 +749,18 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
# undef BOUNDARY
}
static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UNUSED short what, void *v_client) {
static void _http_callback_stream_error(struct bufferevent *buf_event, short what, void *v_client) {
(void)buf_event;
(void)what;
us_stream_client_s *const client = (us_stream_client_s *)v_client;
us_server_s *const server = client->server;
us_server_runtime_s *const run = server->run;
US_LIST_REMOVE_C(_RUN(stream_clients), client, _RUN(stream_clients_count));
US_LIST_REMOVE_C(run->stream_clients, client, run->stream_clients_count);
if (_RUN(stream_clients_count) == 0) {
atomic_store(&_VID(has_clients), false);
if (run->stream_clients_count == 0) {
atomic_store(&server->stream->run->http_has_clients, false);
# ifdef WITH_GPIO
us_gpio_set_has_http_clients(false);
# endif
@@ -760,10 +768,10 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
char *const reason = us_bufferevent_format_reason(what);
US_LOG_INFO("HTTP: DEL client (now=%u): %s, id=%" PRIx64 ", %s",
_RUN(stream_clients_count), client->hostport, client->id, reason);
run->stream_clients_count, client->hostport, client->id, reason);
free(reason);
struct evhttp_connection *const conn = evhttp_request_get_connection(client->request);
struct evhttp_connection *conn = evhttp_request_get_connection(client->request);
US_DELETE(conn, evhttp_connection_free);
free(client->key);
@@ -772,10 +780,13 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
}
static void _http_queue_send_stream(us_server_s *server, bool stream_updated, bool frame_updated) {
us_server_runtime_s *const run = server->run;
us_server_exposed_s *const ex = run->exposed;
bool has_clients = false;
bool queued = false;
US_LIST_ITERATE(_RUN(stream_clients), client, {
US_LIST_ITERATE(run->stream_clients, client, { // cppcheck-suppress constStatement
struct evhttp_connection *const conn = evhttp_request_get_connection(client->request);
if (conn != NULL) {
// Фикс для бага WebKit. При включенной опции дропа одинаковых фреймов,
@@ -813,43 +824,56 @@ static void _http_queue_send_stream(us_server_s *server, bool stream_updated, bo
static long long queued_fps_second = 0;
const long long now = us_floor_ms(us_get_now_monotonic());
if (now != queued_fps_second) {
_EX(queued_fps) = queued_fps_accum;
ex->queued_fps = queued_fps_accum;
queued_fps_accum = 0;
queued_fps_second = now;
}
queued_fps_accum += 1;
} else if (!has_clients) {
_EX(queued_fps) = 0;
ex->queued_fps = 0;
}
}
static void _http_request_watcher(UNUSED int fd, UNUSED short what, void *v_server) {
us_server_s *server = (us_server_s *)v_server;
static void _http_request_watcher(int fd, short what, void *v_server) {
(void)fd;
(void)what;
us_server_s *const server = (us_server_s *)v_server;
us_server_runtime_s *const run = server->run;
const long double now = us_get_now_monotonic();
if (us_stream_has_clients(_RUN(stream))) {
_RUN(last_request_ts) = now;
} else if (_RUN(last_request_ts) + server->exit_on_no_clients < now) {
if (us_stream_has_clients(server->stream)) {
run->last_request_ts = now;
} else if (run->last_request_ts + server->exit_on_no_clients < now) {
US_LOG_INFO("HTTP: No requests or HTTP/sink clients found in last %u seconds, exiting ...",
server->exit_on_no_clients);
us_process_suicide();
_RUN(last_request_ts) = now;
run->last_request_ts = now;
}
}
static void _http_refresher(UNUSED int fd, UNUSED short what, void *v_server) {
static void _http_refresher(int fd, short what, void *v_server) {
(void)fd;
(void)what;
us_server_s *server = (us_server_s *)v_server;
us_server_exposed_s *ex = server->run->exposed;
us_ring_s *const ring = server->stream->run->http_jpeg_ring;
bool stream_updated = false;
bool frame_updated = false;
if (atomic_load(&_VID(updated))) {
frame_updated = _expose_new_frame(server);
const int ri = us_ring_consumer_acquire(ring, 0);
if (ri >= 0) {
const us_frame_s *const frame = ring->items[ri];
frame_updated = _expose_frame(server, frame);
stream_updated = true;
} else if (_EX(expose_end_ts) + 1 < us_get_now_monotonic()) {
us_ring_consumer_release(ring, ri);
} else if (ex->expose_end_ts + 1 < us_get_now_monotonic()) {
US_LOG_DEBUG("HTTP: Repeating exposed ...");
_EX(expose_begin_ts) = us_get_now_monotonic();
_EX(expose_cmp_ts) = _EX(expose_begin_ts);
_EX(expose_end_ts) = _EX(expose_begin_ts);
ex->expose_begin_ts = us_get_now_monotonic();
ex->expose_cmp_ts = ex->expose_begin_ts;
ex->expose_end_ts = ex->expose_begin_ts;
frame_updated = true;
stream_updated = true;
}
@@ -860,62 +884,59 @@ static void _http_refresher(UNUSED int fd, UNUSED short what, void *v_server) {
frame_updated
&& server->notify_parent
&& (
_EX(notify_last_online) != _EX(frame->online)
|| _EX(notify_last_width) != _EX(frame->width)
|| _EX(notify_last_height) != _EX(frame->height)
ex->notify_last_online != ex->frame->online
|| ex->notify_last_width != ex->frame->width
|| ex->notify_last_height != ex->frame->height
)
) {
_EX(notify_last_online) = _EX(frame->online);
_EX(notify_last_width) = _EX(frame->width);
_EX(notify_last_height) = _EX(frame->height);
ex->notify_last_online = ex->frame->online;
ex->notify_last_width = ex->frame->width;
ex->notify_last_height = ex->frame->height;
us_process_notify_parent();
}
}
static bool _expose_new_frame(us_server_s *server) {
bool updated = false;
static bool _expose_frame(us_server_s *server, const us_frame_s *frame) {
us_server_exposed_s *const ex = server->run->exposed;
US_MUTEX_LOCK(_VID(mutex));
US_LOG_DEBUG("HTTP: Updating exposed frame (online=%d) ...", frame->online);
ex->expose_begin_ts = us_get_now_monotonic();
US_LOG_DEBUG("HTTP: Updating exposed frame (online=%d) ...", _VID(frame->online));
_EX(captured_fps) = _VID(captured_fps);
_EX(expose_begin_ts) = us_get_now_monotonic();
if (server->drop_same_frames && _VID(frame->online)) {
if (server->drop_same_frames && frame->online) {
bool need_drop = false;
bool maybe_same = false;
if (
(need_drop = (_EX(dropped) < server->drop_same_frames))
&& (maybe_same = us_frame_compare(_EX(frame), _VID(frame)))
(need_drop = (ex->dropped < server->drop_same_frames))
&& (maybe_same = us_frame_compare(ex->frame, frame))
) {
_EX(expose_cmp_ts) = us_get_now_monotonic();
_EX(expose_end_ts) = _EX(expose_cmp_ts);
ex->expose_cmp_ts = us_get_now_monotonic();
ex->expose_end_ts = ex->expose_cmp_ts;
US_LOG_VERBOSE("HTTP: Dropped same frame number %u; cmp_time=%.06Lf",
_EX(dropped), _EX(expose_cmp_ts) - _EX(expose_begin_ts));
_EX(dropped) += 1;
goto not_updated;
ex->dropped, (ex->expose_cmp_ts - ex->expose_begin_ts));
ex->dropped += 1;
return false; // Not updated
} else {
_EX(expose_cmp_ts) = us_get_now_monotonic();
ex->expose_cmp_ts = us_get_now_monotonic();
US_LOG_VERBOSE("HTTP: Passed same frame check (need_drop=%d, maybe_same=%d); cmp_time=%.06Lf",
need_drop, maybe_same, (_EX(expose_cmp_ts) - _EX(expose_begin_ts)));
need_drop, maybe_same, (ex->expose_cmp_ts - ex->expose_begin_ts));
}
}
us_frame_copy(_VID(frame), _EX(frame));
if (frame->used == 0) {
// Фрейм нулевой длины означает, что мы просто должны повторить то,
// что у нас уже есть, с поправкой на онлайн.
ex->frame->online = frame->online;
} else {
us_frame_copy(frame, ex->frame);
}
_EX(dropped) = 0;
_EX(expose_cmp_ts) = _EX(expose_begin_ts);
_EX(expose_end_ts) = us_get_now_monotonic();
ex->dropped = 0;
ex->expose_cmp_ts = ex->expose_begin_ts;
ex->expose_end_ts = us_get_now_monotonic();
US_LOG_VERBOSE("HTTP: Exposed frame: online=%d, exp_time=%.06Lf",
_EX(frame->online), _EX(expose_end_ts) - _EX(expose_begin_ts));
updated = true;
not_updated:
atomic_store(&_VID(updated), false);
US_MUTEX_UNLOCK(_VID(mutex));
return updated;
ex->frame->online, (ex->expose_end_ts - ex->expose_begin_ts));
return true; // Updated
}
static const char *_http_get_header(struct evhttp_request *request, const char *key) {

View File

@@ -111,7 +111,7 @@ typedef struct {
bool notify_last_online;
unsigned notify_last_width;
unsigned notify_last_height;
} us_exposed_s;
} us_server_exposed_s;
typedef struct {
struct event_base *base;
@@ -124,8 +124,7 @@ typedef struct {
long double last_request_ts;
struct event *refresher;
us_stream_s *stream;
us_exposed_s *exposed;
us_server_exposed_s *exposed;
us_stream_client_s *stream_clients;
unsigned stream_clients_count;
@@ -159,6 +158,8 @@ typedef struct us_server_sx {
bool notify_parent;
unsigned exit_on_no_clients;
us_stream_s *stream;
us_server_runtime_s *run;
} us_server_s;

View File

@@ -33,7 +33,7 @@ char *us_find_static_file_path(const char *root_path, const char *request_path)
}
US_CALLOC(path, strlen(root_path) + strlen(simplified_path) + 16); // + reserved for /index.html
sprintf(path, "%s/%s", root_path, simplified_path);
assert(sprintf(path, "%s/%s", root_path, simplified_path) > 0);
struct stat st;
# define LOAD_STAT { \
@@ -42,14 +42,12 @@ char *us_find_static_file_path(const char *root_path, const char *request_path)
goto error; \
} \
}
LOAD_STAT;
if (S_ISDIR(st.st_mode)) {
US_LOG_VERBOSE("HTTP: Requested static path %s is a directory, trying %s/index.html", path, path);
strcat(path, "/index.html");
LOAD_STAT;
}
# undef LOAD_STAT
if (!S_ISREG(st.st_mode)) {
@@ -64,12 +62,10 @@ char *us_find_static_file_path(const char *root_path, const char *request_path)
goto ok;
error:
US_DELETE(path, free);
path = NULL;
ok:
free(simplified_path);
error:
US_DELETE(path, free);
ok:
free(simplified_path);
return path;
}

View File

@@ -26,6 +26,7 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <sys/stat.h>

View File

@@ -25,19 +25,16 @@
evutil_socket_t us_evhttp_bind_unix(struct evhttp *http, const char *path, bool rm, mode_t mode) {
struct sockaddr_un addr = {0};
const size_t max_sun_path = sizeof(addr.sun_path) - 1;
# define MAX_SUN_PATH (sizeof(addr.sun_path) - 1)
if (strlen(path) > MAX_SUN_PATH) {
US_LOG_ERROR("UNIX socket path is too long; max=%zu", MAX_SUN_PATH);
if (strlen(path) > max_sun_path) {
US_LOG_ERROR("UNIX socket path is too long; max=%zu", max_sun_path);
return -1;
}
strncpy(addr.sun_path, path, MAX_SUN_PATH);
strncpy(addr.sun_path, path, max_sun_path);
addr.sun_family = AF_UNIX;
# undef MAX_SUN_PATH
const evutil_socket_t fd = socket(AF_UNIX, SOCK_STREAM, 0);
assert(fd >= 0);
assert(!evutil_make_socket_nonblocking(fd));

View File

@@ -29,9 +29,9 @@
#include "../libs/tools.h"
#include "../libs/threading.h"
#include "../libs/logging.h"
#include "../libs/device.h"
#include "options.h"
#include "device.h"
#include "encoder.h"
#include "stream.h"
#include "http/server.h"
@@ -52,14 +52,16 @@ static void _block_thread_signals(void) {
assert(!pthread_sigmask(SIG_BLOCK, &mask, NULL));
}
static void *_stream_loop_thread(UNUSED void *arg) {
static void *_stream_loop_thread(void *arg) {
(void)arg;
US_THREAD_RENAME("stream");
_block_thread_signals();
us_stream_loop(_g_stream);
return NULL;
}
static void *_server_loop_thread(UNUSED void *arg) {
static void *_server_loop_thread(void *arg) {
(void)arg;
US_THREAD_RENAME("http");
_block_thread_signals();
us_server_loop(_g_server);

View File

@@ -233,7 +233,7 @@ static int _parse_resolution(const char *str, unsigned *width, unsigned *height,
static int _check_instance_id(const char *str);
static void _features(void);
static void _help(FILE *fp, us_device_s *dev, us_encoder_s *enc, us_stream_s *stream, us_server_s *server);
static void _help(FILE *fp, const us_device_s *dev, const us_encoder_s *enc, const us_stream_s *stream, const us_server_s *server);
us_options_s *us_options_init(unsigned argc, char *argv[]) {
@@ -254,8 +254,6 @@ void us_options_destroy(us_options_s *options) {
US_DELETE(options->raw_sink, us_memsink_destroy);
US_DELETE(options->h264_sink, us_memsink_destroy);
US_DELETE(options->blank, us_frame_destroy);
for (unsigned index = 0; index < options->argc; ++index) {
free(options->argv_copy[index]);
}
@@ -332,8 +330,6 @@ int options_parse(us_options_s *options, us_device_s *dev, us_encoder_s *enc, us
break; \
}
char *blank_path = NULL;
# define ADD_SINK(x_prefix) \
char *x_prefix##_name = NULL; \
mode_t x_prefix##_mode = 0660; \
@@ -372,7 +368,7 @@ int options_parse(us_options_s *options, us_device_s *dev, us_encoder_s *enc, us
case _O_QUALITY: OPT_NUMBER("--quality", dev->jpeg_quality, 1, 100, 0);
case _O_ENCODER: OPT_PARSE("encoder type", enc->type, us_encoder_parse_type, US_ENCODER_TYPE_UNKNOWN, ENCODER_TYPES_STR);
case _O_GLITCHED_RESOLUTIONS: break; // Deprecated
case _O_BLANK: OPT_SET(blank_path, optarg);
case _O_BLANK: break; // Deprecated
case _O_LAST_AS_BLANK: OPT_NUMBER("--last-as-blank", stream->last_as_blank, 0, 86400, 0);
case _O_SLOWDOWN: OPT_SET(stream->slowdown, true);
case _O_DEVICE_TIMEOUT: OPT_NUMBER("--device-timeout", dev->timeout, 1, 60, 0);
@@ -485,9 +481,6 @@ int options_parse(us_options_s *options, us_device_s *dev, us_encoder_s *enc, us
US_LOG_INFO("Starting PiKVM uStreamer %s ...", US_VERSION);
options->blank = us_blank_frame_init(blank_path);
stream->blank = options->blank;
# define ADD_SINK(x_label, x_prefix) { \
if (x_prefix##_name && x_prefix##_name[0] != '\0') { \
options->x_prefix = us_memsink_init( \
@@ -586,7 +579,7 @@ static void _features(void) {
# endif
}
static void _help(FILE *fp, us_device_s *dev, us_encoder_s *enc, us_stream_s *stream, us_server_s *server) {
static void _help(FILE *fp, const us_device_s *dev, const us_encoder_s *enc, const us_stream_s *stream, const us_server_s *server) {
# define SAY(x_msg, ...) fprintf(fp, x_msg "\n", ##__VA_ARGS__)
SAY("\nuStreamer - Lightweight and fast MJPEG-HTTP streamer");
SAY("═══════════════════════════════════════════════════");
@@ -629,12 +622,12 @@ static void _help(FILE *fp, us_device_s *dev, us_encoder_s *enc, us_stream_s *st
SAY(" * M2M-IMAGE ── GPU-accelerated JPEG encoding using V4L2 M2M image interface;");
SAY(" * NOOP ─────── Don't compress MJPEG stream (do nothing).\n");
SAY(" -g|--glitched-resolutions <WxH,...> ─ It doesn't do anything. Still here for compatibility.\n");
SAY(" -k|--blank <path> ─────────────────── Path to JPEG file that will be shown when the device is disconnected");
SAY(" -k|--blank <path> ─────────────────── It doesn't do anything. Still here for compatibility..\n");
SAY(" during the streaming. Default: black screen 640x480 with 'NO SIGNAL'.\n");
SAY(" -K|--last-as-blank <sec> ──────────── Show the last frame received from the camera after it was disconnected,");
SAY(" but no more than specified time (or endlessly if 0 is specified).");
SAY(" If the device has not yet been online, display 'NO SIGNAL' or the image");
SAY(" specified by option --blank. Default: disabled.");
SAY(" If the device has not yet been online, display some error text.");
SAY(" Default: disabled.");
SAY(" Note: currently this option has no effect on memory sinks.\n");
SAY(" -l|--slowdown ─────────────────────── Slowdown capturing to 1 FPS or less when no stream or sink clients");
SAY(" are connected. Useful to reduce CPU consumption. Default: disabled.\n");
@@ -696,7 +689,7 @@ static void _help(FILE *fp, us_device_s *dev, us_encoder_s *enc, us_stream_s *st
ADD_SINK("RAW", "raw-")
ADD_SINK("H264", "h264-")
SAY(" --h264-bitrate <kbps> ───────── H264 bitrate in Kbps. Default: %u.\n", stream->h264_bitrate);
SAY(" --h264-gop <N> ──────────────── Intarval between keyframes. Default: %u.\n", stream->h264_gop);
SAY(" --h264-gop <N> ──────────────── Interval between keyframes. Default: %u.\n", stream->h264_gop);
SAY(" --h264-m2m-device </dev/path> ─ Path to V4L2 M2M encoder device. Default: auto select.\n");
# undef ADD_SINK
# ifdef WITH_GPIO

View File

@@ -27,6 +27,7 @@
#include <stdbool.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>
#include <limits.h>
#include <getopt.h>
#include <errno.h>
@@ -38,10 +39,9 @@
#include "../libs/frame.h"
#include "../libs/memsink.h"
#include "../libs/options.h"
#include "../libs/device.h"
#include "device.h"
#include "encoder.h"
#include "blank.h"
#include "stream.h"
#include "http/server.h"
#ifdef WITH_GPIO
@@ -53,7 +53,6 @@ typedef struct {
unsigned argc;
char **argv;
char **argv_copy;
us_frame_s *blank;
us_memsink_s *sink;
us_memsink_s *raw_sink;
us_memsink_s *h264_sink;

View File

@@ -24,8 +24,7 @@
static us_workers_pool_s *_stream_init_loop(us_stream_s *stream);
static us_workers_pool_s *_stream_init_one(us_stream_s *stream);
static void _stream_expose_frame(us_stream_s *stream, us_frame_s *frame, unsigned captured_fps);
static void _stream_expose_frame(us_stream_s *stream, us_frame_s *frame);
#define _RUN(x_next) stream->run->x_next
@@ -47,15 +46,11 @@ static void _stream_expose_frame(us_stream_s *stream, us_frame_s *frame, unsigne
us_stream_s *us_stream_init(us_device_s *dev, us_encoder_s *enc) {
us_stream_runtime_s *run;
US_CALLOC(run, 1);
US_RING_INIT_WITH_ITEMS(run->http_jpeg_ring, 4, us_frame_init);
atomic_init(&run->http_has_clients, false);
atomic_init(&run->captured_fps, 0);
atomic_init(&run->stop, false);
us_video_s *video;
US_CALLOC(video, 1);
video->frame = us_frame_init();
atomic_init(&video->updated, false);
US_MUTEX_INIT(video->mutex);
atomic_init(&video->has_clients, false);
run->video = video;
run->blank = us_blank_init();
us_stream_s *stream;
US_CALLOC(stream, 1);
@@ -70,16 +65,13 @@ us_stream_s *us_stream_init(us_device_s *dev, us_encoder_s *enc) {
}
void us_stream_destroy(us_stream_s *stream) {
US_MUTEX_DESTROY(_RUN(video->mutex));
us_frame_destroy(_RUN(video->frame));
free(_RUN(video));
us_blank_destroy(stream->run->blank);
US_RING_DELETE_WITH_ITEMS(stream->run->http_jpeg_ring, us_frame_destroy);
free(stream->run);
free(stream);
}
void us_stream_loop(us_stream_s *stream) {
assert(stream->blank != NULL);
US_LOG_INFO("Using V4L2 device: %s", stream->dev->path);
US_LOG_INFO("Using desired FPS: %u", stream->dev->desired_fps);
@@ -90,7 +82,6 @@ void us_stream_loop(us_stream_s *stream) {
for (us_workers_pool_s *pool; (pool = _stream_init_loop(stream)) != NULL;) {
long double grab_after = 0;
unsigned fluency_passed = 0;
unsigned captured_fps = 0;
unsigned captured_fps_accum = 0;
long long captured_fps_second = 0;
@@ -111,10 +102,11 @@ void us_stream_loop(us_stream_s *stream) {
if (!ready_wr->job_failed) {
if (ready_wr->job_timely) {
_stream_expose_frame(stream, ready_job->dest, captured_fps);
US_LOG_PERF("##### Encoded frame exposed; worker=%s", ready_wr->name);
_stream_expose_frame(stream, ready_job->dest);
US_LOG_PERF("##### Encoded JPEG exposed; worker=%s, latency=%.3Lf",
ready_wr->name, us_get_now_monotonic() - ready_job->dest->grab_ts);
} else {
US_LOG_PERF("----- Encoded frame dropped; worker=%s", ready_wr->name);
US_LOG_PERF("----- Encoded JPEG dropped; worker=%s", ready_wr->name);
}
} else {
break;
@@ -126,7 +118,6 @@ void us_stream_loop(us_stream_s *stream) {
unsigned slc = 0;
for (; slc < 10 && !atomic_load(&_RUN(stop)) && !us_stream_has_clients(stream); ++slc) {
usleep(100000);
++slc;
}
h264_force_key = (slc == 10);
}
@@ -136,9 +127,8 @@ void us_stream_loop(us_stream_s *stream) {
}
bool has_read;
bool has_write;
bool has_error;
const int selected = us_device_select(stream->dev, &has_read, &has_write, &has_error);
const int selected = us_device_select(stream->dev, &has_read, &has_error);
if (selected < 0) {
if (errno != EINTR) {
@@ -151,8 +141,6 @@ void us_stream_loop(us_stream_s *stream) {
# endif
} else {
if (has_read) {
US_LOG_DEBUG("Frame is ready");
# ifdef WITH_GPIO
us_gpio_set_stream_online(true);
# endif
@@ -175,10 +163,10 @@ void us_stream_loop(us_stream_s *stream) {
fluency_passed = 0;
if (now_second != captured_fps_second) {
captured_fps = captured_fps_accum;
US_LOG_PERF_FPS("A new second has come; captured_fps=%u", captured_fps_accum);
atomic_store(&stream->run->captured_fps, captured_fps_accum);
captured_fps_accum = 0;
captured_fps_second = now_second;
US_LOG_PERF_FPS("A new second has come; captured_fps=%u", captured_fps);
}
captured_fps_accum += 1;
@@ -198,25 +186,13 @@ void us_stream_loop(us_stream_s *stream) {
}
}
// Это условие было добавлено из параноидальных соображений, и мы ни разу не сталкивались
// с подобными ошибками, кроме случая с libcamerify, который генерит эвенты на запись.
// Судя по всему, игнорирование has_write не делает никому плохо.
/*if (has_write) {
US_LOG_ERROR("Got unexpected writing event, seems device was disconnected");
if (has_error && us_device_consume_event(stream->dev) < 0) {
break;
}*/
if (has_error) {
US_LOG_INFO("Got V4L2 event");
if (us_device_consume_event(stream->dev) < 0) {
break;
}
}
}
}
us_workers_pool_destroy(pool);
us_device_switch_capturing(stream->dev, false);
us_device_close(stream->dev);
# ifdef WITH_GPIO
@@ -233,7 +209,7 @@ void us_stream_loop_break(us_stream_s *stream) {
bool us_stream_has_clients(us_stream_s *stream) {
return (
atomic_load(&_RUN(video->has_clients))
atomic_load(&_RUN(http_has_clients))
// has_clients синков НЕ обновляются в реальном времени
|| (stream->sink != NULL && atomic_load(&stream->sink->has_clients))
|| (_RUN(h264) != NULL && /*_RUN(h264->sink) == NULL ||*/ atomic_load(&_RUN(h264->sink->has_clients)))
@@ -241,79 +217,63 @@ bool us_stream_has_clients(us_stream_s *stream) {
}
static us_workers_pool_s *_stream_init_loop(us_stream_s *stream) {
us_workers_pool_s *pool = NULL;
int access_error = 0;
US_LOG_DEBUG("%s: stream->run->stop=%d", __FUNCTION__, atomic_load(&_RUN(stop)));
int access_errno = 0;
while (!atomic_load(&_RUN(stop))) {
_stream_expose_frame(stream, NULL, 0);
atomic_store(&stream->run->captured_fps, 0);
_stream_expose_frame(stream, NULL);
if (access(stream->dev->path, R_OK|W_OK) < 0) {
if (access_error != errno) {
if (access_errno != errno) {
US_SEP_INFO('=');
US_LOG_PERROR("Can't access device");
US_LOG_INFO("Waiting for the device access ...");
access_error = errno;
access_errno = errno;
}
sleep(stream->error_delay);
continue;
} else {
US_SEP_INFO('=');
access_error = 0;
goto sleep_and_retry;
}
if ((pool = _stream_init_one(stream)) == NULL) {
US_LOG_INFO("Sleeping %u seconds before new stream init ...", stream->error_delay);
sleep(stream->error_delay);
} else {
break;
US_SEP_INFO('=');
access_errno = 0;
stream->dev->dma_export = (
stream->enc->type == US_ENCODER_TYPE_M2M_VIDEO
|| stream->enc->type == US_ENCODER_TYPE_M2M_IMAGE
|| _RUN(h264) != NULL
);
if (us_device_open(stream->dev) == 0) {
return us_encoder_workers_pool_init(stream->enc, stream->dev);
}
US_LOG_INFO("Sleeping %u seconds before new stream init ...", stream->error_delay);
sleep_and_retry:
sleep(stream->error_delay);
}
return pool;
return NULL;
}
static us_workers_pool_s *_stream_init_one(us_stream_s *stream) {
if (us_device_open(stream->dev) < 0) {
goto error;
}
if (
stream->enc->type == US_ENCODER_TYPE_M2M_VIDEO
|| stream->enc->type == US_ENCODER_TYPE_M2M_IMAGE
|| (_RUN(h264) && !us_is_jpeg(stream->dev->run->format))
) {
us_device_export_to_dma(stream->dev);
}
if (us_device_switch_capturing(stream->dev, true) < 0) {
goto error;
}
return us_encoder_workers_pool_init(stream->enc, stream->dev);
error:
us_device_close(stream->dev);
return NULL;
}
static void _stream_expose_frame(us_stream_s *stream, us_frame_s *frame, unsigned captured_fps) {
# define VID(x_next) _RUN(video->x_next)
static void _stream_expose_frame(us_stream_s *stream, us_frame_s *frame) {
us_stream_runtime_s *const run = stream->run;
us_blank_s *const blank = run->blank;
us_frame_s *new = NULL;
US_MUTEX_LOCK(VID(mutex));
if (frame != NULL) {
new = frame;
_RUN(last_as_blank_ts) = 0; // Останавливаем таймер
US_LOG_DEBUG("Exposed ALIVE video frame");
} else {
if (VID(frame->used == 0)) {
new = stream->blank; // Инициализация
_RUN(last_as_blank_ts) = 0;
unsigned width = stream->dev->run->width;
unsigned height = stream->dev->run->height;
if (width == 0 || height == 0) {
width = stream->dev->width;
height = stream->dev->height;
}
us_blank_draw(blank, "< NO SIGNAL >", width, height);
} else if (VID(frame->online)) { // Если переходим из online в offline
if (run->last_online) { // Если переходим из online в offline
if (stream->last_as_blank < 0) { // Если last_as_blank выключен, просто покажем старую картинку
new = stream->blank;
new = blank->jpeg;
US_LOG_INFO("Changed video frame to BLANK");
} else if (stream->last_as_blank > 0) { // // Если нужен таймер - запустим
_RUN(last_as_blank_ts) = us_get_now_monotonic() + stream->last_as_blank;
@@ -321,9 +281,8 @@ static void _stream_expose_frame(us_stream_s *stream, us_frame_s *frame, unsigne
} else { // last_as_blank == 0 - показываем последний фрейм вечно
US_LOG_INFO("Freezed last ALIVE video frame forever");
}
} else if (stream->last_as_blank < 0) {
new = stream->blank;
new = blank->jpeg;
// US_LOG_INFO("Changed video frame to BLANK");
}
@@ -332,28 +291,38 @@ static void _stream_expose_frame(us_stream_s *stream, us_frame_s *frame, unsigne
&& _RUN(last_as_blank_ts) != 0
&& _RUN(last_as_blank_ts) < us_get_now_monotonic()
) {
new = stream->blank;
_RUN(last_as_blank_ts) = 0; // // Останавливаем таймер
new = blank->jpeg;
_RUN(last_as_blank_ts) = 0; // Останавливаем таймер
US_LOG_INFO("Changed last ALIVE video frame to BLANK");
}
}
if (new != NULL) {
us_frame_copy(new, VID(frame));
int ri = -1;
while (
!atomic_load(&_RUN(stop))
&& ((ri = us_ring_producer_acquire(run->http_jpeg_ring, 0)) < 0)
) {
US_LOG_ERROR("Can't push JPEG to HTTP ring (no free slots)");
}
if (ri < 0) {
return;
}
VID(frame->online) = (frame != NULL);
VID(captured_fps) = captured_fps;
atomic_store(&VID(updated), true);
US_MUTEX_UNLOCK(VID(mutex));
us_frame_s *const dest = run->http_jpeg_ring->items[ri];
if (new == NULL) {
dest->used = 0;
dest->online = false;
} else {
us_frame_copy(new, dest);
dest->online = true;
}
run->last_online = (frame != NULL);
us_ring_producer_release(run->http_jpeg_ring, ri);
new = (frame ? frame : stream->blank);
_SINK_PUT(sink, new);
_SINK_PUT(sink, (frame != NULL ? frame : blank->jpeg));
if (frame == NULL) {
_SINK_PUT(raw_sink, stream->blank);
_H264_PUT(stream->blank, false);
_SINK_PUT(raw_sink, blank->raw);
_H264_PUT(blank->raw, false);
}
# undef VID
}

View File

@@ -35,11 +35,12 @@
#include "../libs/tools.h"
#include "../libs/threading.h"
#include "../libs/logging.h"
#include "../libs/ring.h"
#include "../libs/frame.h"
#include "../libs/memsink.h"
#include "../libs/device.h"
#include "blank.h"
#include "device.h"
#include "encoder.h"
#include "workers.h"
#include "h264.h"
@@ -49,20 +50,17 @@
typedef struct {
us_frame_s *frame;
unsigned captured_fps;
atomic_bool updated;
pthread_mutex_t mutex;
us_ring_s *http_jpeg_ring;
atomic_bool http_has_clients;
atomic_uint captured_fps;
atomic_bool has_clients; // For slowdown
} us_video_s;
typedef struct {
us_video_s *video;
bool last_online;
long double last_as_blank_ts;
us_h264_stream_s *h264;
us_blank_s *blank;
atomic_bool stop;
} us_stream_runtime_s;
@@ -70,7 +68,6 @@ typedef struct {
us_device_s *dev;
us_encoder_s *enc;
us_frame_s *blank;
int last_as_blank;
bool slowdown;
unsigned error_delay;

View File

@@ -157,7 +157,7 @@ void us_workers_pool_assign(us_workers_pool_s *pool, us_worker_s *ready_wr/*, vo
US_MUTEX_UNLOCK(pool->free_workers_mutex);
}
long double us_workers_pool_get_fluency_delay(us_workers_pool_s *pool, us_worker_s *ready_wr) {
long double us_workers_pool_get_fluency_delay(us_workers_pool_s *pool, const us_worker_s *ready_wr) {
const long double approx_job_time = pool->approx_job_time * 0.9 + ready_wr->last_job_time * 0.1;
US_LOG_VERBOSE("Correcting pool's %s approx_job_time: %.3Lf -> %.3Lf (last_job_time=%.3Lf)",

View File

@@ -92,4 +92,4 @@ void us_workers_pool_destroy(us_workers_pool_s *pool);
us_worker_s *us_workers_pool_wait(us_workers_pool_s *pool);
void us_workers_pool_assign(us_workers_pool_s *pool, us_worker_s *ready_wr/*, void *job*/);
long double us_workers_pool_get_fluency_delay(us_workers_pool_s *pool, us_worker_s *ready_wr);
long double us_workers_pool_get_fluency_delay(us_workers_pool_s *pool, const us_worker_s *ready_wr);

633
src/v4p/drm.c Normal file
View File

@@ -0,0 +1,633 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "drm.h"
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <linux/videodev2.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#include <drm_fourcc.h>
#include <libdrm/drm.h>
#include "../libs/types.h"
#include "../libs/tools.h"
#include "../libs/logging.h"
#include "../libs/frame.h"
#include "../libs/frametext.h"
static void _drm_vsync_callback(int fd, uint n_frame, uint sec, uint usec, void *v_run);
static int _drm_expose_raw(us_drm_s *drm, const us_frame_s *frame);
static void _drm_cleanup(us_drm_s *drm);
static int _drm_ensure(us_drm_s *drm, const us_frame_s *frame, float hz);
static int _drm_check_status(us_drm_s *drm);
static int _drm_find_sink(us_drm_s *drm, uint width, uint height, float hz);
static int _drm_init_buffers(us_drm_s *drm);
static int _drm_start_video(us_drm_s *drm);
static u32 _find_crtc(int fd, drmModeRes *res, drmModeConnector *conn, u32 *taken_crtcs);
static const char *_connector_type_to_string(u32 type);
static float _get_refresh_rate(const drmModeModeInfo *mode);
#define _D_LOG_ERROR(x_msg, ...) US_LOG_ERROR("DRM: " x_msg, ##__VA_ARGS__)
#define _D_LOG_PERROR(x_msg, ...) US_LOG_PERROR("DRM: " x_msg, ##__VA_ARGS__)
#define _D_LOG_INFO(x_msg, ...) US_LOG_INFO("DRM: " x_msg, ##__VA_ARGS__)
#define _D_LOG_VERBOSE(x_msg, ...) US_LOG_VERBOSE("DRM: " x_msg, ##__VA_ARGS__)
#define _D_LOG_DEBUG(x_msg, ...) US_LOG_DEBUG("DRM: " x_msg, ##__VA_ARGS__)
us_drm_s *us_drm_init(void) {
us_drm_runtime_s *run;
US_CALLOC(run, 1);
run->fd = -1;
run->status_fd = -1;
run->ft = us_frametext_init();
run->state = US_DRM_STATE_CLOSED;
us_drm_s *drm;
US_CALLOC(drm, 1);
drm->path = "/dev/dri/card0";
drm->port = "HDMI-A-1";
drm->n_bufs = 4;
drm->timeout = 5;
drm->run = run;
return drm;
}
void us_drm_destroy(us_drm_s *drm) {
_drm_cleanup(drm);
us_frametext_destroy(drm->run->ft);
US_DELETE(drm->run, free);
US_DELETE(drm, free); // cppcheck-suppress uselessAssignmentPtrArg
}
int us_drm_wait_for_vsync(us_drm_s *drm) {
us_drm_runtime_s *const run = drm->run;
if (_drm_ensure(drm, NULL, 0) < 0) {
return -1;
}
if (run->has_vsync) {
return 0;
}
struct timeval timeout = {.tv_sec = drm->timeout};
fd_set fds;
FD_ZERO(&fds);
FD_SET(run->fd, &fds);
_D_LOG_DEBUG("Calling select() for VSync ...");
const int result = select(run->fd + 1, &fds, NULL, NULL, &timeout);
if (result < 0) {
_D_LOG_PERROR("Can't select(%d) device for VSync", run->fd);
goto error;
} else if (result == 0) {
_D_LOG_ERROR("Device timeout while waiting VSync");
goto error;
}
drmEventContext ctx = {
.version = DRM_EVENT_CONTEXT_VERSION,
.page_flip_handler = _drm_vsync_callback,
};
_D_LOG_DEBUG("Handling DRM event (maybe VSync) ...");
if (drmHandleEvent(run->fd, &ctx) < 0) {
_D_LOG_PERROR("Can't handle DRM event");
goto error;
}
return 0;
error:
_drm_cleanup(drm);
_D_LOG_ERROR("Device destroyed due an error (vsync)");
return -1;
}
int us_drm_expose(us_drm_s *drm, us_drm_expose_e ex, const us_frame_s *frame, float hz) {
us_drm_runtime_s *const run = drm->run;
if (_drm_ensure(drm, frame, hz) < 0) {
return -1;
}
const drmModeModeInfo *const mode = &run->mode;
bool msg_drawn = false;
# define DRAW_MSG(x_msg) { \
us_frametext_draw(run->ft, (x_msg), mode->hdisplay, mode->vdisplay); \
frame = run->ft->frame; \
msg_drawn = true; \
}
if (frame == NULL) {
switch (ex) {
case US_DRM_EXPOSE_NO_SIGNAL:
DRAW_MSG("=== PiKVM ===\n \n< NO SIGNAL >");
break;
case US_DRM_EXPOSE_BUSY:
DRAW_MSG("=== PiKVM ===\n \n< ONLINE IS ACTIVE >");
break;
default:
DRAW_MSG("=== PiKVM ===\n \n< ??? >");
}
} else if (mode->hdisplay != frame->width/* || mode->vdisplay != frame->height*/) {
// XXX: At least we'll try to show something instead of nothing ^^^
char msg[1024];
US_SNPRINTF(msg, 1023,
"=== PiKVM ==="
"\n \n< UNSUPPORTED RESOLUTION >"
"\n \n< %ux%up%.02f >"
"\n \nby this display",
frame->width, frame->height, hz);
DRAW_MSG(msg);
} else if (frame->format != V4L2_PIX_FMT_RGB24) {
DRAW_MSG(
"=== PiKVM ==="
"\n \n< UNSUPPORTED CAPTURE FORMAT >"
"\n \nIt shouldn't happen ever."
"\n \nPlease check the logs and report a bug:"
"\n \n- https://github.com/pikvm/pikvm -");
}
# undef DRAW_MSG
if (_drm_expose_raw(drm, frame) < 0) {
_drm_cleanup(drm);
_D_LOG_ERROR("Device destroyed due an error (expose)");
}
return (msg_drawn ? -1 : 0);
}
static void _drm_vsync_callback(int fd, uint n_frame, uint sec, uint usec, void *v_run) {
(void)fd;
(void)n_frame;
(void)sec;
(void)usec;
us_drm_runtime_s *const run = v_run;
run->has_vsync = true;
_D_LOG_DEBUG("Got VSync signal");
}
static int _drm_expose_raw(us_drm_s *drm, const us_frame_s *frame) {
us_drm_runtime_s *const run = drm->run;
us_drm_buffer_s *const buf = &run->bufs[run->next_n_buf];
_D_LOG_DEBUG("Exposing%s framebuffer n_buf=%u, vsync=%d ...",
(frame == NULL ? " EMPTY" : ""), run->next_n_buf, run->has_vsync);
if (frame == NULL) {
memset(buf->data, 0, buf->allocated);
} else {
memcpy(buf->data, frame->data, US_MIN(frame->used, buf->allocated));
}
run->has_vsync = false;
const int retval = drmModePageFlip(
run->fd, run->crtc_id, buf->id,
DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_PAGE_FLIP_ASYNC,
run);
run->next_n_buf = (run->next_n_buf + 1) % run->n_bufs;
return retval;
}
static void _drm_cleanup(us_drm_s *drm) {
us_drm_runtime_s *const run = drm->run;
_D_LOG_DEBUG("Cleaning up ...");
if (run->saved_crtc != NULL) {
if (drmModeSetCrtc(run->fd,
run->saved_crtc->crtc_id, run->saved_crtc->buffer_id,
run->saved_crtc->x, run->saved_crtc->y,
&run->conn_id, 1, &run->saved_crtc->mode
) < 0 && errno != ENOENT) {
_D_LOG_PERROR("Can't restore CRTC");
}
drmModeFreeCrtc(run->saved_crtc);
run->saved_crtc = NULL;
}
if (run->bufs != NULL) {
for (uint n_buf = 0; n_buf < run->n_bufs; ++n_buf) {
us_drm_buffer_s *const buf = &run->bufs[n_buf];
if (buf->data != NULL && munmap(buf->data, buf->allocated)) {
_D_LOG_PERROR("Can't unmap buffer=%u", n_buf);
}
if (buf->fb_added && drmModeRmFB(run->fd, buf->id) < 0) {
_D_LOG_PERROR("Can't remove buffer=%u", n_buf);
}
if (buf->dumb_created) {
struct drm_mode_destroy_dumb destroy = {.handle = buf->handle};
if (drmIoctl(run->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy) < 0) {
_D_LOG_PERROR("Can't destroy dumb buffer=%u", n_buf);
}
}
}
US_DELETE(run->bufs, free);
run->n_bufs = 0;
}
US_CLOSE_FD(run->status_fd);
US_CLOSE_FD(run->fd);
run->crtc_id = 0;
run->next_n_buf = 0;
run->has_vsync = false;
if (run->state == US_DRM_STATE_OK) {
_D_LOG_INFO("Stopped");
}
run->state = US_DRM_STATE_CLOSED;
}
static int _drm_ensure(us_drm_s *drm, const us_frame_s *frame, float hz) {
us_drm_runtime_s *const run = drm->run;
switch (_drm_check_status(drm)) {
case 0: break;
case -2: goto unplugged;
default: goto error;
}
if (frame == NULL && run->state == US_DRM_STATE_OK) {
return 0;
} else if (
frame != NULL
&& run->p_width == frame->width
&& run->p_height == frame->height
&& run->p_hz == hz
&& run->state <= US_DRM_STATE_CLOSED
) {
return (run->state == US_DRM_STATE_OK ? 0 : -1);
}
const us_drm_state_e saved_state = run->state;
_drm_cleanup(drm);
if (saved_state > US_DRM_STATE_CLOSED) {
run->state = saved_state;
}
run->p_width = (frame != NULL ? frame->width : 0); // 0 for find the native resolution
run->p_height = (frame != NULL ? frame->height : 0);
run->p_hz = hz;
_D_LOG_INFO("Configuring DRM device ...");
if ((run->fd = open(drm->path, O_RDWR | O_CLOEXEC | O_NONBLOCK)) < 0) {
_D_LOG_PERROR("Can't open DRM device");
goto error;
}
# define CHECK_CAP(x_cap) { \
u64 m_check; \
if (drmGetCap(run->fd, x_cap, &m_check) < 0) { \
_D_LOG_PERROR("Can't check " #x_cap); \
goto error; \
} \
if (!m_check) { \
_D_LOG_ERROR(#x_cap " is not supported"); \
goto error; \
} \
}
CHECK_CAP(DRM_CAP_DUMB_BUFFER);
// CHECK_CAP(DRM_CAP_PRIME);
# undef CHECK_CAP
switch (_drm_find_sink(drm, run->p_width, run->p_height, run->p_hz)) {
case 0: break;
case -2: goto unplugged;
default: goto error;
}
const float mode_hz = _get_refresh_rate(&run->mode);
if (frame == NULL) {
run->p_width = run->mode.hdisplay;
run->p_height = run->mode.vdisplay;
run->p_hz = mode_hz;
}
_D_LOG_INFO("Using %s mode: %ux%up%.02f",
drm->port, run->mode.hdisplay, run->mode.vdisplay, mode_hz);
if (_drm_init_buffers(drm) < 0) {
goto error;
}
if (_drm_start_video(drm) < 0) {
goto error;
}
_D_LOG_INFO("Showing ...");
run->state = US_DRM_STATE_OK;
return 0;
error:
_drm_cleanup(drm);
_D_LOG_ERROR("Device destroyed due an error (ensure)");
return -1;
unplugged:
if (run->state != US_DRM_STATE_NO_DISPLAY) {
_D_LOG_INFO("Display %s is not plugged", drm->port);
}
_drm_cleanup(drm);
run->state = US_DRM_STATE_NO_DISPLAY;
return -2;
}
static int _drm_check_status(us_drm_s *drm) {
us_drm_runtime_s *run = drm->run;
if (run->status_fd < 0) {
struct stat st;
if (stat(drm->path, &st) < 0) {
_D_LOG_PERROR("Can't stat() DRM device");
goto error;
}
const uint mi = minor(st.st_rdev);
char path[128];
US_SNPRINTF(path, 127, "/sys/class/drm/card%u-%s/status", mi, drm->port);
if ((run->status_fd = open(path, O_RDONLY | O_CLOEXEC)) < 0) {
_D_LOG_PERROR("Can't open DRM device status file: %s", path);
goto error;
}
}
char status_ch;
if (read(run->status_fd, &status_ch, 1) != 1) {
_D_LOG_PERROR("Can't read connector status");
goto error;
}
if (lseek(run->status_fd, 0, SEEK_SET) != 0) {
_D_LOG_PERROR("Can't rewind connector status");
goto error;
}
return (status_ch == 'd' ? -2 : 0);
error:
US_CLOSE_FD(run->status_fd);
return -1;
}
static int _drm_find_sink(us_drm_s *drm, uint width, uint height, float hz) {
us_drm_runtime_s *const run = drm->run;
run->crtc_id = 0;
_D_LOG_DEBUG("Trying to find the appropriate sink ...");
drmModeRes *res = drmModeGetResources(run->fd);
if (res == NULL) {
_D_LOG_PERROR("Can't get resources info");
goto done;
}
if (res->count_connectors <= 0) {
_D_LOG_ERROR("Can't find any connectors");
goto done;
}
for (int ci = 0; ci < res->count_connectors; ++ci) {
drmModeConnector *conn = drmModeGetConnector(run->fd, res->connectors[ci]);
if (conn == NULL) {
_D_LOG_PERROR("Can't get connector index=%d", ci);
goto done;
}
char port[32];
US_SNPRINTF(port, 31, "%s-%u",
_connector_type_to_string(conn->connector_type),
conn->connector_type_id);
if (strcmp(port, drm->port) != 0) {
drmModeFreeConnector(conn);
continue;
}
_D_LOG_DEBUG("Found connector for port %s: conn_type=%d, conn_type_id=%d",
drm->port, conn->connector_type, conn->connector_type_id);
if (conn->connection != DRM_MODE_CONNECTED) {
_D_LOG_DEBUG("Display is not connected");
drmModeFreeConnector(conn);
goto done;
}
drmModeModeInfo *best = NULL;
drmModeModeInfo *closest = NULL;
drmModeModeInfo *pref = NULL;
for (int mi = 0; mi < conn->count_modes; ++mi) {
drmModeModeInfo *const mode = &conn->modes[mi];
if (mode->flags & DRM_MODE_FLAG_INTERLACE) {
continue; // Paranoia for size and discard interlaced
}
const float mode_hz = _get_refresh_rate(mode);
if (mode->hdisplay == width && mode->vdisplay == height) {
best = mode; // Any mode with exact resolution
if (hz > 0 && mode_hz == hz) {
break; // Exact mode with same freq
}
}
if (mode->hdisplay == width && mode->vdisplay < height) {
if (closest == NULL || _get_refresh_rate(closest) != hz) {
closest = mode; // Something like 1920x1080p60 for 1920x1200p60 source
}
}
if (pref == NULL && (mode->type & DRM_MODE_TYPE_PREFERRED)) {
pref = mode; // Preferred mode if nothing is found
}
}
if (best == NULL) { best = closest; }
if (best == NULL) { best = pref; }
if (best == NULL) { best = (conn->count_modes > 0 ? &conn->modes[0] : NULL); }
if (best == NULL) {
_D_LOG_ERROR("Can't find any appropriate resolutions");
drmModeFreeConnector(conn);
goto unplugged;
}
assert(best->hdisplay > 0);
assert(best->vdisplay > 0);
u32 taken_crtcs = 0; // Unused here
if ((run->crtc_id = _find_crtc(run->fd, res, conn, &taken_crtcs)) == 0) {
_D_LOG_ERROR("Can't find CRTC");
drmModeFreeConnector(conn);
goto done;
}
run->conn_id = conn->connector_id;
memcpy(&run->mode, best, sizeof(drmModeModeInfo));
drmModeFreeConnector(conn);
break;
}
done:
drmModeFreeResources(res);
return (run->crtc_id > 0 ? 0 : -1);
unplugged:
drmModeFreeResources(res);
return -2;
}
static int _drm_init_buffers(us_drm_s *drm) {
us_drm_runtime_s *const run = drm->run;
_D_LOG_DEBUG("Initializing %u buffers ...", drm->n_bufs);
US_CALLOC(run->bufs, drm->n_bufs);
for (run->n_bufs = 0; run->n_bufs < drm->n_bufs; ++run->n_bufs) {
const uint n_buf = run->n_bufs;
us_drm_buffer_s *const buf = &run->bufs[n_buf];
struct drm_mode_create_dumb create = {
.width = run->mode.hdisplay,
.height = run->mode.vdisplay,
.bpp = 24,
};
if (drmIoctl(run->fd, DRM_IOCTL_MODE_CREATE_DUMB, &create) < 0) {
_D_LOG_PERROR("Can't create dumb buffer=%u", n_buf);
return -1;
}
buf->handle = create.handle;
buf->dumb_created = true;
u32 handles[4] = {create.handle};
u32 strides[4] = {create.pitch};
u32 offsets[4] = {0};
if (drmModeAddFB2(
run->fd,
run->mode.hdisplay, run->mode.vdisplay, DRM_FORMAT_RGB888,
handles, strides, offsets, &buf->id, 0
)) {
_D_LOG_PERROR("Can't setup buffer=%u", n_buf);
return -1;
}
buf->fb_added = true;
struct drm_mode_map_dumb map = {.handle = create.handle};
if (drmIoctl(run->fd, DRM_IOCTL_MODE_MAP_DUMB, &map) < 0) {
_D_LOG_PERROR("Can't prepare dumb buffer=%u to mapping", n_buf);
return -1;
}
if ((buf->data = mmap(
NULL, create.size,
PROT_READ | PROT_WRITE, MAP_SHARED,
run->fd, map.offset
)) == MAP_FAILED) {
_D_LOG_PERROR("Can't map buffer=%u", n_buf);
return -1;
}
memset(buf->data, 0, create.size);
buf->allocated = create.size;
}
return 0;
}
static int _drm_start_video(us_drm_s *drm) {
us_drm_runtime_s *const run = drm->run;
run->saved_crtc = drmModeGetCrtc(run->fd, run->crtc_id);
_D_LOG_DEBUG("Setting up CRTC ...");
if (drmModeSetCrtc(run->fd, run->crtc_id, run->bufs[0].id, 0, 0, &run->conn_id, 1, &run->mode) < 0) {
_D_LOG_PERROR("Can't set CRTC");
return -1;
}
if (_drm_expose_raw(drm, NULL) < 0) {
_D_LOG_PERROR("Can't flip the first page");
return -1;
}
return 0;
}
static u32 _find_crtc(int fd, drmModeRes *res, drmModeConnector *conn, u32 *taken_crtcs) {
for (int ei = 0; ei < conn->count_encoders; ++ei) {
drmModeEncoder *enc = drmModeGetEncoder(fd, conn->encoders[ei]);
if (enc == NULL) {
continue;
}
for (int ci = 0; ci < res->count_crtcs; ++ci) {
u32 bit = (1 << ci);
if (!(enc->possible_crtcs & bit)) {
continue; // Not compatible
}
if (*taken_crtcs & bit) {
continue; // Already taken
}
drmModeFreeEncoder(enc);
*taken_crtcs |= bit;
return res->crtcs[ci];
}
drmModeFreeEncoder(enc);
}
return 0;
}
static const char *_connector_type_to_string(u32 type) {
switch (type) {
# define CASE_NAME(x_suffix, x_name) \
case DRM_MODE_CONNECTOR_##x_suffix: return x_name;
CASE_NAME(VGA, "VGA");
CASE_NAME(DVII, "DVI-I");
CASE_NAME(DVID, "DVI-D");
CASE_NAME(DVIA, "DVI-A");
CASE_NAME(Composite, "Composite");
CASE_NAME(SVIDEO, "SVIDEO");
CASE_NAME(LVDS, "LVDS");
CASE_NAME(Component, "Component");
CASE_NAME(9PinDIN, "DIN");
CASE_NAME(DisplayPort, "DP");
CASE_NAME(HDMIA, "HDMI-A");
CASE_NAME(HDMIB, "HDMI-B");
CASE_NAME(TV, "TV");
CASE_NAME(eDP, "eDP");
CASE_NAME(VIRTUAL, "Virtual");
CASE_NAME(DSI, "DSI");
CASE_NAME(DPI, "DPI");
CASE_NAME(WRITEBACK, "Writeback");
CASE_NAME(SPI, "SPI");
CASE_NAME(USB, "USB");
case DRM_MODE_CONNECTOR_Unknown: break;
# undef CASE_NAME
}
return "Unknown";
}
static float _get_refresh_rate(const drmModeModeInfo *mode) {
int mhz = (mode->clock * 1000000LL / mode->htotal + mode->vtotal / 2) / mode->vtotal;
if (mode->flags & DRM_MODE_FLAG_INTERLACE) {
mhz *= 2;
}
if (mode->flags & DRM_MODE_FLAG_DBLSCAN) {
mhz /= 2;
}
if (mode->vscan > 1) {
mhz /= mode->vscan;
}
return (float)mhz / 1000;
}

90
src/v4p/drm.h Normal file
View File

@@ -0,0 +1,90 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <xf86drmMode.h>
#include "../libs/types.h"
#include "../libs/frame.h"
#include "../libs/frametext.h"
typedef enum {
US_DRM_EXPOSE_FRAME = 0,
US_DRM_EXPOSE_NO_SIGNAL,
US_DRM_EXPOSE_BUSY,
} us_drm_expose_e;
typedef enum {
US_DRM_STATE_OK = 0,
US_DRM_STATE_CLOSED,
US_DRM_STATE_NO_DISPLAY,
} us_drm_state_e;
typedef struct {
u32 id;
u32 handle;
u8 *data;
uz allocated;
bool dumb_created;
bool fb_added;
} us_drm_buffer_s;
typedef struct {
int status_fd;
int fd;
u32 crtc_id;
u32 conn_id;
drmModeModeInfo mode;
us_drm_buffer_s *bufs;
uint n_bufs;
drmModeCrtc *saved_crtc;
uint next_n_buf;
bool has_vsync;
us_frametext_s *ft;
uint p_width;
uint p_height;
float p_hz;
us_drm_state_e state;
} us_drm_runtime_s;
typedef struct {
char *path;
char *port;
uint n_bufs;
uint timeout;
us_drm_runtime_s *run;
} us_drm_s;
us_drm_s *us_drm_init(void);
void us_drm_destroy(us_drm_s *drm);
int us_drm_wait_for_vsync(us_drm_s *drm);
int us_drm_expose(us_drm_s *drm, us_drm_expose_e ex, const us_frame_s *frame, float hz);

322
src/v4p/main.c Normal file
View File

@@ -0,0 +1,322 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <stdatomic.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <getopt.h>
#include <errno.h>
#include <assert.h>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include "../libs/types.h"
#include "../libs/const.h"
#include "../libs/tools.h"
#include "../libs/logging.h"
#include "../libs/device.h"
#include "../libs/options.h"
#include "drm.h"
enum _OPT_VALUES {
_O_UNIX_FOLLOW = 'f',
_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[] = {
{"unix-follow", required_argument, NULL, _O_UNIX_FOLLOW},
{"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 atomic_bool _g_stop = false;
atomic_bool _g_ustreamer_online = false;
static void _signal_handler(int signum);
static void _install_signal_handlers(void);
static void _main_loop();
static void *_follower_thread(void *v_unix_follow);
static void _slowdown(void);
static void _help(FILE *fp);
int main(int argc, char *argv[]) {
US_LOGGING_INIT;
US_THREAD_RENAME("main");
char *unix_follow = NULL;
# 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; \
}
char short_opts[128];
us_build_short_options(_LONG_OPTS, short_opts, 128);
for (int ch; (ch = getopt_long(argc, argv, short_opts, _LONG_OPTS, NULL)) >= 0;) {
switch (ch) {
case _O_UNIX_FOLLOW: OPT_SET(unix_follow, optarg);
case _O_LOG_LEVEL: OPT_NUMBER("--log-level", us_g_log_level, US_LOG_LEVEL_INFO, US_LOG_LEVEL_DEBUG, 0);
case _O_PERF: OPT_SET(us_g_log_level, US_LOG_LEVEL_PERF);
case _O_VERBOSE: OPT_SET(us_g_log_level, US_LOG_LEVEL_VERBOSE);
case _O_DEBUG: OPT_SET(us_g_log_level, US_LOG_LEVEL_DEBUG);
case _O_FORCE_LOG_COLORS: OPT_SET(us_g_log_colored, true);
case _O_NO_LOG_COLORS: OPT_SET(us_g_log_colored, false);
case _O_HELP: _help(stdout); return 0;
case _O_VERSION: puts(US_VERSION); return 0;
case 0: break;
default: return 1;
}
}
# undef OPT_NUMBER
# undef OPT_SET
_install_signal_handlers();
pthread_t follower_tid;
if (unix_follow != NULL) {
US_THREAD_CREATE(follower_tid, _follower_thread, unix_follow);
}
_main_loop(unix_follow);
if (unix_follow != NULL) {
US_THREAD_JOIN(follower_tid);
}
US_LOGGING_DESTROY;
return 0;
}
static void _signal_handler(int signum) {
char *const name = us_signum_to_string(signum);
US_LOG_INFO_NOLOCK("===== Stopping by %s =====", name);
free(name);
atomic_store(&_g_stop, true);
}
static void _install_signal_handlers(void) {
struct sigaction sig_act = {0};
assert(!sigemptyset(&sig_act.sa_mask));
sig_act.sa_handler = _signal_handler;
assert(!sigaddset(&sig_act.sa_mask, SIGINT));
assert(!sigaddset(&sig_act.sa_mask, SIGTERM));
assert(!sigaddset(&sig_act.sa_mask, SIGPIPE));
US_LOG_DEBUG("Installing SIGINT handler ...");
assert(!sigaction(SIGINT, &sig_act, NULL));
US_LOG_DEBUG("Installing SIGTERM handler ...");
assert(!sigaction(SIGTERM, &sig_act, NULL));
US_LOG_DEBUG("Installing SIGTERM handler ...");
assert(!sigaction(SIGPIPE, &sig_act, NULL));
}
static void _main_loop(void) {
us_drm_s *drm = us_drm_init();
drm->port = "HDMI-A-2";
us_device_s *dev = us_device_init();
dev->path = "/dev/kvmd-video";
dev->n_bufs = drm->n_bufs;
dev->format = V4L2_PIX_FMT_RGB24;
dev->dv_timings = true;
dev->persistent = true;
while (!atomic_load(&_g_stop)) {
if (atomic_load(&_g_ustreamer_online)) {
if (us_drm_wait_for_vsync(drm) == 0) {
us_drm_expose(drm, US_DRM_EXPOSE_BUSY, NULL, 0);
}
if (dev->run->fd >= 0) {
goto close;
} else {
_slowdown();
continue;
}
}
if (us_device_open(dev) < 0) {
goto close;
}
while (!atomic_load(&_g_stop)) {
if (atomic_load(&_g_ustreamer_online)) {
goto close;
}
if (us_drm_wait_for_vsync(drm) < 0) {
_slowdown();
continue;
}
bool has_read;
bool has_error;
const int selected = us_device_select(dev, &has_read, &has_error);
if (selected < 0) {
if (errno != EINTR) {
US_LOG_PERROR("Mainloop select() error");
goto close;
}
} else if (selected == 0) { // Persistent timeout
if (us_drm_expose(drm, US_DRM_EXPOSE_NO_SIGNAL, NULL, 0) < 0) {
_slowdown();
continue;
}
} else {
if (has_read) {
us_hw_buffer_s *hw;
const int buf_index = us_device_grab_buffer(dev, &hw);
if (buf_index >= 0) {
const int exposed = us_drm_expose(drm, US_DRM_EXPOSE_FRAME, &hw->raw, dev->run->hz);
if (us_device_release_buffer(dev, hw) < 0) {
goto close;
}
if (exposed < 0) {
_slowdown();
continue;
}
} else if (buf_index != -2) { // -2 for broken frame
goto close;
}
}
if (has_error && us_device_consume_event(dev) < 0) {
goto close;
}
}
}
close:
us_device_close(dev);
_slowdown();
}
us_device_destroy(dev);
us_drm_destroy(drm);
}
static void *_follower_thread(void *v_unix_follow) {
const char *path = v_unix_follow;
assert(path != NULL);
US_THREAD_RENAME("follower");
while (!atomic_load(&_g_stop)) {
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
assert(fd >= 0);
struct sockaddr_un addr = {0};
strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
addr.sun_family = AF_UNIX;
const bool online = !connect(fd, (struct sockaddr *)&addr, sizeof(addr));
atomic_store(&_g_ustreamer_online, online);
US_CLOSE_FD(fd); // cppcheck-suppress unreadVariable
usleep(200 * 1000);
}
return NULL;
}
static void _slowdown(void) {
if (!atomic_load(&_g_stop)) {
usleep(500 * 1000);
}
}
static void _help(FILE *fp) {
# define SAY(_msg, ...) fprintf(fp, _msg "\n", ##__VA_ARGS__)
SAY("\nuStreamer-V4P - Video passthrough for PiKVM V4 Plus");
SAY("═════════════════════════════════════════════════════");
SAY("Version: %s; license: GPLv3", US_VERSION);
SAY("Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com>\n");
SAY("Example:");
SAY("════════");
SAY(" ustreamer-v4p\n");
SAY("Passthrough options:");
SAY("════════════════════");
SAY(" -f|--unix-follow <path> ──────── Pause the process if the specified socked exists.\n");
SAY("Logging options:");
SAY("════════════════");
SAY(" --log-level <N> ──── Verbosity level of messages from 0 (info) to 3 (debug).");
SAY(" Enabling debugging messages can slow down the program.");
SAY(" Available levels: 0 (info), 1 (performance), 2 (verbose), 3 (debug).");
SAY(" Default: %d.\n", us_g_log_level);
SAY(" --perf ───────────── Enable performance messages (same as --log-level=1). Default: disabled.\n");
SAY(" --verbose ────────── Enable verbose messages and lower (same as --log-level=2). Default: disabled.\n");
SAY(" --debug ──────────── Enable debug messages and lower (same as --log-level=3). Default: disabled.\n");
SAY(" --force-log-colors ─ Force color logging. Default: colored if stderr is a TTY.\n");
SAY(" --no-log-colors ──── Disable color logging. Default: ditto.\n");
SAY("Help options:");
SAY("═════════════");
SAY(" -h|--help ─────── Print this text and exit.\n");
SAY(" -v|--version ──── Print version and exit.\n");
# undef SAY
}

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