Compare commits

...

323 Commits
v3.3 ... v5.21

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

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

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

* Small tweaks

* Small changes

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

* Phrasing, structure

* Remove MJPEG primer and rewrite intro

* Link to library files directly

* Adjust page title

* Remove todo

* Resolve todo (Janus setup)

* Comment all JavaScript

* Rephrase to use active voice, clarify details

* Phrasing and clarifications

* Write backend instructions

* Grammar

* Use generic URL with port number

* Use consistent terminology

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

* Link “building” section of uStreamer README

* Use active voice

* Change page title to “demo”

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

* Use window location to construct server URL

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

* Elaborate why we clone the media stream

* Drop obsolete `WITH_OMX` option

* Correct path of shared object file

* Fix shadowing variable of same name

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

* Add `refcount.h` fix

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

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

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

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

Relates to issue #40
2021-02-12 00:06:03 +01:00
Michael Lynch
45a4d148f5 Delete trailing whitespace from README 2021-02-05 15:19:10 -05:00
Michael Lynch
93ab12b54b Add libjpeg-dev to Ubuntu 20.04 dependencies
Building on Ubuntu 20.04 fails without libjpeg-dev, so this change adds it to the README.
2021-02-05 15:18:26 -05:00
Devaev Maxim
fb07444b70 Bump version: 3.15 → 3.16 2021-02-04 01:55:16 +03:00
Devaev Maxim
a8ba2f7364 H264: disable MMAL_PARAMETER_VIDEO_ENCODE_FRAME_LIMIT_BITS 2021-02-04 01:54:42 +03:00
Devaev Maxim
171bcb315a Bump version: 3.14 → 3.15 2021-02-03 05:49:40 +03:00
Devaev Maxim
911df1af15 rename 2021-02-03 05:48:15 +03:00
Devaev Maxim
a165ff4523 expose keyframe flag 2021-02-03 05:39:18 +03:00
Devaev Maxim
5cfb3b1e60 Bump version: 3.13 → 3.14 2021-01-28 22:16:44 +03:00
Devaev Maxim
82b3b78238 ignore drop-same-frames for sinks 2021-01-27 11:30:35 +03:00
Devaev Maxim
8a82ff6691 using archlinux/archlinux:base-devel 2021-01-25 11:58:52 +03:00
Devaev Maxim
2807678551 Bump version: 3.12 → 3.13 2021-01-25 03:26:26 +03:00
Devaev Maxim
e96e0aa73c workaround for unreasonable rebuilding in package() 2021-01-25 03:21:31 +03:00
Devaev Maxim
d06c2619b2 added missing options to man 2021-01-24 12:26:43 +03:00
Devaev Maxim
6b18455d11 systemd-tmpfiles hangs 2021-01-24 12:21:08 +03:00
Maxim Devaev
217977d9cb Merge pull request #87 from reedy/patch-1
Fix casing of macros
2021-01-22 05:22:26 +03:00
Maxim Devaev
b9f186e47c Merge pull request #88 from reedy/patch-2
Fix below typo
2021-01-22 05:21:44 +03:00
Sam Reed
d7d56f3536 Fix below typo 2021-01-22 01:45:40 +00:00
Sam Reed
2e3c764369 Fix casing of macros 2021-01-22 01:43:50 +00:00
Devaev Maxim
7236e53813 Bump version: 3.11 → 3.12 2021-01-22 02:00:35 +03:00
Devaev Maxim
e7f7350405 fix 2021-01-21 23:49:37 +03:00
Devaev Maxim
81f0266a87 drop_same_frames in python 2021-01-21 23:37:06 +03:00
Devaev Maxim
eb1a2e3695 Bump version: 3.10 → 3.11 2021-01-21 18:55:17 +03:00
Devaev Maxim
6cfd3739d3 fixed python context manager 2021-01-21 18:54:39 +03:00
Devaev Maxim
77b8386de7 Bump version: 3.9 → 3.10 2021-01-21 16:59:47 +03:00
Devaev Maxim
a002bd3427 Makefile deps fix 2021-01-21 16:59:10 +03:00
Devaev Maxim
202b907430 Bump version: 3.8 → 3.9 2021-01-21 12:47:40 +03:00
Devaev Maxim
d9f4aba953 fixed build 2021-01-21 12:47:05 +03:00
Devaev Maxim
1b08857534 Bump version: 3.7 → 3.8 2021-01-21 12:03:35 +03:00
Devaev Maxim
eec19892fa fixed PKGBUILD 2021-01-21 12:03:01 +03:00
Devaev Maxim
40abe73391 Bump version: 3.6 → 3.7 2021-01-21 11:51:34 +03:00
Devaev Maxim
e4f1ef654f lint fix 2021-01-21 11:34:24 +03:00
Devaev Maxim
fa6afb96ce check usleep() retval 2021-01-21 11:22:04 +03:00
Devaev Maxim
c3f98b34f2 python builddeps 2021-01-21 09:27:29 +03:00
Devaev Maxim
b7b3e8e87d python: handle signals 2021-01-21 09:18:09 +03:00
Devaev Maxim
94383a2d54 moved python module 2021-01-21 07:19:10 +03:00
Devaev Maxim
184a4879eb gzip force 2021-01-21 05:23:22 +03:00
Devaev Maxim
bf48908c59 fixed another segfault 2021-01-21 05:22:35 +03:00
Devaev Maxim
66afbccf21 fixed segfault 2021-01-21 03:01:50 +03:00
Devaev Maxim
97dbe59aea handle usleep error 2021-01-21 02:56:43 +03:00
Devaev Maxim
d874fdeaec gitignore 2021-01-20 16:06:42 +03:00
Devaev Maxim
97e3938d56 trying to eliminate memory leaks 2021-01-20 16:06:32 +03:00
Devaev Maxim
87c7e8063f fixed shm umask 2021-01-20 15:17:13 +03:00
Devaev Maxim
fe5beb0114 fixed python destdir 2021-01-20 14:24:12 +03:00
Devaev Maxim
eec8e41b2b Bump version: 3.5 → 3.6 2021-01-20 14:03:48 +03:00
Devaev Maxim
87bff56a78 python module 2021-01-20 14:03:08 +03:00
Devaev Maxim
34e0e4dab4 moved flock_timedwait_monotonic() to tools.h 2021-01-20 12:36:37 +03:00
Devaev Maxim
e08ac1467f moved memsink_shared_s to separate file 2021-01-20 12:28:46 +03:00
Devaev Maxim
bc25e787cc buf fix 2021-01-20 12:27:28 +03:00
Devaev Maxim
5f11caf6fc added protocol version to memsink 2021-01-20 02:21:03 +03:00
Devaev Maxim
9be264e176 memsink magic 2021-01-18 11:27:57 +03:00
Devaev Maxim
3d28dcbaff improved memsink logic 2021-01-18 11:05:04 +03:00
Devaev Maxim
61c3b44c8a raw sink 2021-01-17 14:33:18 +03:00
Devaev Maxim
e26973a9f1 don't show help on option error 2021-01-17 09:37:44 +03:00
Devaev Maxim
598e2372e5 refactoring 2021-01-17 09:35:55 +03:00
Devaev Maxim
4fb8c7745c client ttl; some refactoring 2021-01-17 08:38:13 +03:00
Devaev Maxim
14131f0b54 check memsink clients 2021-01-17 00:06:44 +03:00
Devaev Maxim
de41c9653e pluggable outputs for the future 2021-01-15 12:38:32 +03:00
Devaev Maxim
66e0bb0a2c Bump version: 3.4 → 3.5 2021-01-15 01:30:17 +03:00
Devaev Maxim
5af18e8b70 fixed segfault on uninitialized mmal 2021-01-15 01:26:13 +03:00
Devaev Maxim
b746bc307c Bump version: 3.3 → 3.4 2021-01-13 17:46:08 +03:00
Devaev Maxim
3fd3aab909 banned option --as-needed from LDFLAGS 2021-01-13 17:45:33 +03:00
137 changed files with 6668 additions and 3955 deletions

View File

@@ -1,14 +1,26 @@
[bumpversion]
commit = True
tag = True
current_version = 3.3
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
current_version = 5.21
parse = (?P<major>\d+)\.(?P<minor>\d+)
serialize =
{major}.{minor}
[bumpversion:file:src/libs/config.h]
search = VERSION "{current_version}"
replace = VERSION "{new_version}"
[bumpversion:file:src/libs/const.h]
parse = (?P<major>\d+)
serialize = {major}
search = VERSION_MAJOR {current_version}
replace = VERSION_MAJOR {new_version}
[bumpversion:file:./src/libs/const.h]
parse = <major>\d+\.(?P<minor>\d+)
serialize = {minor}
search = VERSION_MINOR {current_version}
replace = VERSION_MINOR {new_version}
[bumpversion:file:python/setup.py]
search = version="{current_version}"
replace = version="{new_version}"
[bumpversion:file:pkg/arch/PKGBUILD]
search = pkgver={current_version}

View File

@@ -4,3 +4,6 @@
# Allow source code
!Makefile
!src/**
!python/**
!janus/**
!man/**

10
.editorconfig Normal file
View File

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

2
.github/FUNDING.yml vendored
View File

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

18
.gitignore vendored
View File

@@ -2,12 +2,16 @@
/linters/.mypy_cache/
/pkg/arch/pkg/
/pkg/arch/src/
/pkg/arch/v*.tar.gz
/pkg/arch/ustreamer-*.pkg.tar.xz
/pkg/arch/ustreamer-*.pkg.tar.zst
/build/
/config.mk
/vgcore.*
/src/build/
/python/build/
/janus/build/
/ustreamer
/ustreamer-dump
/*.sock
/config.mk
vgcore.*
*.sock
*.so
*.bin
*.pkg.tar.xz
*.pkg.tar.zst
*.tar.gz

179
Makefile
View File

@@ -1,153 +1,93 @@
-include config.mk
USTR ?= ustreamer
DUMP ?= ustreamer-dump
DESTDIR ?=
PREFIX ?= /usr/local
MANPREFIX ?= $(PREFIX)/share/man
CC ?= gcc
CFLAGS ?= -O3 -MD
PY ?= python3
CFLAGS ?= -O3
LDFLAGS ?=
RPI_VC_HEADERS ?= /opt/vc/include
RPI_VC_LIBS ?= /opt/vc/lib
BUILD ?= build
export
LINTERS_IMAGE ?= $(USTR)-linters
_LINTERS_IMAGE ?= ustreamer-linters
# =====
override CFLAGS += -c -std=c11 -Wall -Wextra -D_GNU_SOURCE
_COMMON_LIBS = -lm -ljpeg -pthread -lrt
_USTR_LIBS = $(_COMMON_LIBS) -levent -levent_pthreads -luuid
_USTR_SRCS = $(shell ls \
src/libs/*.c \
src/ustreamer/*.c \
src/ustreamer/http/*.c \
src/ustreamer/data/*.c \
src/ustreamer/encoders/cpu/*.c \
src/ustreamer/encoders/hw/*.c \
)
_DUMP_LIBS = $(_COMMON_LIBS)
_DUMP_SRCS = $(shell ls \
src/libs/*.c \
src/dump/*.c \
)
define optbool
$(filter $(shell echo $(1) | tr A-Z a-z), yes on 1)
endef
ifneq ($(call optbool,$(WITH_OMX)),)
_USTR_LIBS += -lbcm_host -lvcos -lvcsm -lopenmaxil -lmmal -lmmal_core -lmmal_util -lmmal_vc_client -lmmal_components -L$(RPI_VC_LIBS)
override CFLAGS += -DWITH_OMX -DOMX_SKIP64BIT -I$(RPI_VC_HEADERS)
_USTR_SRCS += $(shell ls \
src/ustreamer/encoders/omx/*.c \
src/ustreamer/h264/*.c \
)
endif
ifneq ($(call optbool,$(WITH_GPIO)),)
_USTR_LIBS += -lgpiod
override CFLAGS += -DWITH_GPIO
_USTR_SRCS += $(shell ls src/ustreamer/gpio/*.c)
endif
WITH_PTHREAD_NP ?= 1
ifneq ($(call optbool,$(WITH_PTHREAD_NP)),)
override CFLAGS += -DWITH_PTHREAD_NP
endif
WITH_SETPROCTITLE ?= 1
ifneq ($(call optbool,$(WITH_SETPROCTITLE)),)
ifeq ($(shell uname -s | tr A-Z a-z),linux)
_USTR_LIBS += -lbsd
endif
override CFLAGS += -DWITH_SETPROCTITLE
endif
# =====
all: $(USTR) $(DUMP)
all:
+ $(MAKE) apps
ifneq ($(call optbool,$(WITH_PYTHON)),)
+ $(MAKE) python
endif
ifneq ($(call optbool,$(WITH_JANUS)),)
+ $(MAKE) janus
endif
install: $(USTR) $(DUMP)
mkdir -p $(DESTDIR)$(PREFIX)/bin $(DESTDIR)$(MANPREFIX)/man1
install -m755 $(USTR) $(DESTDIR)$(PREFIX)/bin/$(USTR)
install -m755 $(DUMP) $(DESTDIR)$(PREFIX)/bin/$(DUMP)
install -m644 man/$(USTR).1 $(DESTDIR)$(MANPREFIX)/man1/$(USTR).1
install -m644 man/$(DUMP).1 $(DESTDIR)$(MANPREFIX)/man1/$(DUMP).1
gzip $(DESTDIR)$(MANPREFIX)/man1/$(USTR).1
gzip $(DESTDIR)$(MANPREFIX)/man1/$(DUMP).1
apps:
$(MAKE) -C src
@ ln -sf src/ustreamer.bin ustreamer
@ ln -sf src/ustreamer-dump.bin ustreamer-dump
python:
$(MAKE) -C python
@ ln -sf python/build/lib.*/*.so .
janus:
$(MAKE) -C janus
@ ln -sf janus/*.so .
install: all
$(MAKE) -C src install
ifneq ($(call optbool,$(WITH_PYTHON)),)
$(MAKE) -C python install
endif
ifneq ($(call optbool,$(WITH_JANUS)),)
$(MAKE) -C janus install
endif
mkdir -p $(DESTDIR)$(MANPREFIX)/man1
for man in $(shell ls man); do \
install -m644 man/$$man $(DESTDIR)$(MANPREFIX)/man1/$$man; \
gzip -f $(DESTDIR)$(MANPREFIX)/man1/$$man; \
done
install-strip: install
strip $(DESTDIR)$(PREFIX)/bin/$(USTR)
strip $(DESTDIR)$(PREFIX)/bin/$(DUMP)
uninstall:
rm -f $(DESTDIR)$(PREFIX)/bin/$(USTR) \
$(DESTDIR)$(PREFIX)/bin/$(DUMP) \
$(DESTDIR)$(MANPREFIX)/man1/$(USTR).1 \
$(DESTDIR)$(MANPREFIX)/man1/$(DUMP).1
$(MAKE) -C src install-strip
regen:
tools/make-jpeg-h.py src/ustreamer/data/blank.jpeg src/ustreamer/data/blank_jpeg.c BLANK
tools/make-html-h.py src/ustreamer/data/index.html src/ustreamer/data/index_html.c INDEX
$(USTR): $(_USTR_SRCS:%.c=$(BUILD)/%.o)
# $(info ========================================)
$(info == LD $@)
@ $(CC) $^ -o $@ $(LDFLAGS) $(_USTR_LIBS)
# $(info :: CC = $(CC))
# $(info :: LIBS = $(_USTR_LIBS))
# $(info :: CFLAGS = $(CFLAGS))
# $(info :: LDFLAGS = $(LDFLAGS))
$(DUMP): $(_DUMP_SRCS:%.c=$(BUILD)/%.o)
# $(info ========================================)
$(info == LD $@)
@ $(CC) $^ -o $@ $(LDFLAGS) $(_DUMP_LIBS)
# $(info :: CC = $(CC))
# $(info :: LIBS = $(_DUMP_LIBS))
# $(info :: CFLAGS = $(CFLAGS))
# $(info :: LDFLAGS = $(LDFLAGS))
$(BUILD)/%.o: %.c
$(info -- CC $<)
@ mkdir -p $(dir $@) || true
@ $(CC) $< -o $@ $(CFLAGS)
tools/$(MAKE)-jpeg-h.py src/ustreamer/data/blank.jpeg src/ustreamer/data/blank_jpeg.c BLANK
tools/$(MAKE)-html-h.py src/ustreamer/data/index.html src/ustreamer/data/index_html.c INDEX
release:
make clean
make tox
make push
make bump V=$(V)
make push
make clean
$(MAKE) clean
$(MAKE) tox
$(MAKE) push
$(MAKE) bump V=$(V)
$(MAKE) push
$(MAKE) clean
tox: linters
time docker run --rm \
--volume `pwd`:/src:ro \
--volume `pwd`/linters:/src/linters:rw \
-t $(LINTERS_IMAGE) bash -c " \
-t $(_LINTERS_IMAGE) bash -c " \
cd /src \
&& tox -q -c linters/tox.ini $(if $(E),-e $(E),-p auto) \
"
@@ -157,7 +97,7 @@ linters:
docker build \
$(if $(call optbool,$(NC)),--no-cache,) \
--rm \
--tag $(LINTERS_IMAGE) \
--tag $(_LINTERS_IMAGE) \
-f linters/Dockerfile linters
@@ -173,14 +113,15 @@ push:
clean-all: linters clean
- docker run --rm \
--volume `pwd`:/src \
-it $(LINTERS_IMAGE) bash -c "cd src && rm -rf linters/{.tox,.mypy_cache}"
-it $(_LINTERS_IMAGE) bash -c "cd src && rm -rf linters/{.tox,.mypy_cache}"
clean:
rm -rf pkg/arch/pkg pkg/arch/src pkg/arch/v*.tar.gz pkg/arch/ustreamer-*.pkg.tar.{xz,zst}
rm -rf $(USTR) $(DUMP) $(BUILD) vgcore.* *.sock
rm -f ustreamer ustreamer-dump *.so
$(MAKE) -C src clean
$(MAKE) -C python clean
$(MAKE) -C janus clean
.PHONY: linters
_OBJS = $(_USTR_SRCS:%.c=$(BUILD)/%.o) $(_DUMP_SRCS:%.c=$(BUILD)/%.o)
-include $(_OBJS:%.o=%.d)
.PHONY: python janus linters

View File

@@ -4,28 +4,29 @@
[[Русская версия]](README.ru.md)
µStreamer is a lightweight and very quick server to stream [MJPG](https://en.wikipedia.org/wiki/Motion_JPEG) video from any V4L2 device to the net. All new browsers have native support of this video format, as well as most video players such as mplayer, VLC etc.
µStreamer is a part of the [Pi-KVM](https://github.com/pikvm/pikvm) project designed to stream [VGA](https://www.amazon.com/dp/B0126O0RDC) and [HDMI](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/) screencast hardware data with the highest resolution and FPS possible.
µStreamer is a lightweight and very quick server to stream [MJPEG](https://en.wikipedia.org/wiki/Motion_JPEG) video from any V4L2 device to the net. All new browsers have native support of this video format, as well as most video players such as mplayer, VLC etc.
µStreamer is a part of the [PiKVM](https://github.com/pikvm/pikvm) project designed to stream [VGA](https://www.amazon.com/dp/B0126O0RDC) and [HDMI](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/) screencast hardware data with the highest resolution and FPS possible.
µStreamer is very similar to [mjpg-streamer](https://github.com/jacksonliam/mjpg-streamer) with ```input_uvc.so``` and ```output_http.so``` plugins, however, there are some major differences. The key ones are:
| **Feature** | **µStreamer** | **mjpg-streamer** |
|----------|---------------|-------------------|
| Multithreaded JPEG encoding | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| [OpenMAX IL](https://www.khronos.org/openmaxil) hardware acceleration<br>on Raspberry Pi | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Behavior when the device<br>is disconnected while streaming | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Shows a black screen<br>with ```NO SIGNAL``` on it<br>until reconnected | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Stops the streaming <sup>1</sup> |
| [DV-timings](https://linuxtv.org/downloads/v4l-dvb-apis-new/userspace-api/v4l/dv-timings.html) support -<br>the ability to change resolution<br>on the fly by source signal | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) Partially yes <sup>1</sup> |
| Option to skip frames when streaming<br>static images by HTTP to save the traffic | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes <sup>2</sup> | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Streaming via UNIX domain socket | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Debug logs without recompiling,<br>performance statistics log,<br>access to HTTP streaming parameters | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Option to serve files<br>with a built-in HTTP server | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) Regular files only |
| Signaling about the stream state<br>on GPIO using [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Access to webcam controls (focus, servos)<br>and settings such as brightness via HTTP | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes |
| Compatibility with mjpg-streamer's API | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | :) |
| Multithreaded JPEG encoding | ✔ | ✘ |
| Hardware image encoding<br>on Raspberry Pi | ✔ | ✘ |
| Behavior when the device<br>is disconnected while streaming | Shows a black screen<br>with ```NO SIGNAL``` on it<br>until reconnected | Stops the streaming <sup>1</sup> |
| [DV-timings](https://linuxtv.org/downloads/v4l-dvb-apis-new/userspace-api/v4l/dv-timings.html) support -<br>the ability to change resolution<br>on the fly by source signal | ✔ | ☹ Partially yes <sup>1</sup> |
| Option to skip frames when streaming<br>static images by HTTP to save the traffic | ✔ <sup>2</sup> | ✘ |
| Streaming via UNIX domain socket | ✔ | ✘ |
| Systemd socket activation | ✔ | ✘ |
| Debug logs without recompiling,<br>performance statistics log,<br>access to HTTP streaming parameters | ✔ | ✘ |
| Option to serve files<br>with a built-in HTTP server | ✔ | ☹ Regular files only |
| Signaling about the stream state<br>on GPIO using [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) | ✔ | ✘ |
| Access to webcam controls (focus, servos)<br>and settings such as brightness via HTTP | ✘ | |
| Compatibility with mjpg-streamer's API | ✔ | :) |
Footnotes:
* ```1``` Long before µStreamer, I made a [patch](https://github.com/jacksonliam/mjpg-streamer/pull/164) to add DV-timings support to mjpg-streamer and to keep it from hanging up no device disconnection. Alas, the patch is far from perfect and I can't guarantee it will work every time - mjpg-streamer's source code is very complicated and its structure is hard to understand. With this in mind, along with needing multithreading and JPEG hardware acceleration in the future, I decided to make my own stream server from scratch instead of supporting legacy code.
* ```2``` This feature allows to cut down outgoing traffic several-fold when streaming HDMI, but it increases CPU usage a little bit. The idea is that HDMI is a fully digital interface and each captured frame can be identical to the previous one byte-wise. There's no need to stream the same image over the net several times a second. With the `--drop-same-frames=20` option enabled, µStreamer will drop all the matching frames (with a limit of 20 in a row). Each new frame is matched with the previous one first by length, then using ```memcmp()```.
-----
@@ -34,14 +35,14 @@ If you're going to live-stream from your backyard webcam and need to control it,
-----
# Building
You'll need ```make```, ```gcc```, ```libevent``` with ```pthreads``` support, ```libjpeg8```/```libjpeg-turbo```, ```libuuid``` and ```libbsd``` (only for Linux).
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 libjpeg8-dev uuid-dev libbsd-dev`. Add `libraspberrypi-dev` for `WITH_OMX=1` and `libgpiod` for `WITH_GPIO=1`.
* Debian: `sudo apt install build-essential libevent-dev libjpeg62-turbo-dev uuid-dev libbsd-dev`.
* Ubuntu 20.04 x86_64: `sudo apt install build-essential libevent-dev libjpeg62-dev uuid-dev libbsd-dev make gcc libjpeg8 libjpeg-turbo8 libuuid1 libbsd0`.
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev libbsd-dev`. Add `libgpiod-dev` for `WITH_GPIO=1` and `libsystemd-dev` for `WITH_SYSTEMD=1`.
* Debian/Ubuntu: `sudo apt install build-essential libevent-dev libjpeg-dev libbsd-dev`.
* Alpine: `sudo apk add libevent-dev libbsd-dev libjpeg-turbo-dev musl-dev`. Build with `WITH_PTHREAD_NP=0`.
On Raspberry Pi you can build the program with OpenMAX IL. To do this pass option ```WITH_OMX=1``` to ```make```. 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```.
To enable GPIO support install [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) and pass option ```WITH_GPIO=1```. If the compiler reports about a missing function ```pthread_get_name_np()``` (or similar), add option ```WITH_PTHREAD_NP=0``` (it's enabled by default). For the similar error with ```setproctitle()``` add option ```WITH_SETPROCTITLE=0```.
```
$ git clone --depth=1 https://github.com/pikvm/ustreamer
@@ -50,11 +51,14 @@ $ make
$ ./ustreamer --help
```
AUR has a package for Arch Linux: https://aur.archlinux.org/packages/ustreamer. It should compile automatically with OpenMAX IL on Raspberry Pi, if the corresponding headers are present in ```/opt/vc/include```.
AUR has a package for Arch Linux: https://aur.archlinux.org/packages/ustreamer.
FreeBSD port: https://www.freshports.org/multimedia/ustreamer.
-----
# Usage
**For M2M hardware encoding on Raspberry Pi, you need at least 5.15.32 kernel. OpenMAX and MMAL support on older kernels is deprecated and removed.**
Without arguments, ```ustreamer``` will try to open ```/dev/video0``` with 640x480 resolution and start streaming on ```http://127.0.0.1:8080```. You can override this behavior using parameters ```--device```, ```--host``` and ```--port```. For example, to stream to the world, run:
```
# ./ustreamer --device=/dev/video1 --host=0.0.0.0 --port=80
@@ -66,8 +70,8 @@ The recommended way of running µStreamer with [Auvidea B101](https://www.raspbe
```bash
$ ./ustreamer \
--format=uyvy \ # Device input format
--encoder=omx \ # Hardware encoding with OpenMAX
--workers=3 \ # Maximum workers for OpenMAX
--encoder=m2m-image \ # Hardware encoding on V4L2 M2M driver
--workers=3 \ # Workers number
--persistent \ # Don't re-initialize device on timeout (for example when HDMI cable was disconnected)
--dv-timings \ # Use DV-timings
--drop-same-frames=30 # Save the traffic
@@ -77,14 +81,59 @@ $ ./ustreamer \
You can always view the full list of options with ```ustreamer --help```.
-----
# Raspberry Pi Camera Example
Example usage for the Raspberry Pi v1 camera:
```bash
$ sudo modprobe bcm2835-v4l2
$ ./ustreamer --host :: -m jpeg --device-timeout=5 --buffers=3 -r 2592x1944
```
:exclamation: Please note that newer camera models have a different maximum resolution. You can see the supported resolutions at the [PiCamera documentation](https://picamera.readthedocs.io/en/release-1.13/fov.html#sensor-modes).
:exclamation: If you get a poor framerate, it could be that the camera is switched to photo mode, which produces a low framerate (but a higher quality picture). This is because `bcm2835-v4l2` switches to photo mode at resolutions higher than `1280x720`. To work around this, pass the `max_video_width` and `max_video_height` module parameters like so:
```bash
$ modprobe bcm2835-v4l2 max_video_width=2592 max_video_height=1944
```
-----
# Integrations
## Janus
µStreamer supports bandwidth-efficient streaming using [H.264 compression](https://en.wikipedia.org/wiki/Advanced_Video_Coding) and the Janus WebRTC server. See the [Janus integration guide](docs/h264.md) for full details.
## Nginx
When uStreamer is behind an Nginx proxy, it's buffering behavior introduces latency into the video stream. It's possible to disable Nginx's buffering to eliminate the additional latency:
```nginx
location /stream {
postpone_output 0;
proxy_buffering off;
proxy_ignore_headers X-Accel-Buffering;
proxy_pass http://ustreamer;
}
```
-----
# Tips & tricks for v4l2
v4l2 utilities provide the tools to manage USB webcam setting and information. Scripts can be use to make adjustments and run manually or with cron. Running in cron for example to change the exposure settings at certain times of day. The package is available in all Linux distributions and is usually called `v4l-utils`.
* List of available video devices: `v4l2-ctl --list-devices`.
* List available control settings: `v4l2-ctl -d /dev/video0 --list-ctrls`.
* List available video formats: `v4l2-ctl -d /dev/video0 --list-formats-ext`.
* Read the current setting: `v4l2-ctl -d /dev/video0 --get-ctrl=exposure_auto`.
* Change the setting value: `v4l2-ctl -d /dev/video0 --set-ctrl=exposure_auto=1`.
[Here](https://www.kurokesu.com/main/2016/01/16/manual-usb-camera-settings-in-linux/) you can find more examples. Documentation is available in [`man v4l2-ctl`](https://www.mankier.com/1/v4l2-ctl).
-----
# See also
* [Running uStreamer via systemd service](https://github.com/pikvm/ustreamer/issues/16).
* [uStreamer Ansible Role](https://github.com/mtlynch/ansible-role-ustreamer): Use [Ansible](https://docs.ansible.com/ansible/latest/index.html) to compile uStreamer and install it as a systemd service automatically.
-----
# License
Copyright (C) 2018-2021 by Maxim Devaev mdevaev@gmail.com
Copyright (C) 2018-2022 by Maxim Devaev mdevaev@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@@ -5,27 +5,28 @@
[[English version]](README.md)
µStreamer - это маленький и очень быстрый сервер, который позволяет организовать трансляцию видео в формате [MJPG](https://en.wikipedia.org/wiki/Motion_JPEG) с любого устройства V4L2 в сеть. Этот формат нативно поддерживается всеми современными браузерами и большинством приложений для просмотра видео (mplayer, VLC и так далее). µStreamer был разработан в рамках проекта [Pi-KVM](https://github.com/pikvm/pikvm) специально для стриминга с устройств видеозахвата [VGA](https://www.amazon.com/dp/B0126O0RDC) и [HDMI](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/) с максимально возможным разрешением и FPS, которые только позволяет железо.
µStreamer - это маленький и очень быстрый сервер, который позволяет организовать трансляцию видео в формате [MJPEG](https://en.wikipedia.org/wiki/Motion_JPEG) с любого устройства V4L2 в сеть. Этот формат нативно поддерживается всеми современными браузерами и большинством приложений для просмотра видео (mplayer, VLC и так далее). µStreamer был разработан в рамках проекта [PiKVM](https://github.com/pikvm/pikvm) специально для стриминга с устройств видеозахвата [VGA](https://www.amazon.com/dp/B0126O0RDC) и [HDMI](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/) с максимально возможным разрешением и FPS, которые только позволяет железо.
Функционально µStreamer очень похож на [mjpg-streamer](https://github.com/jacksonliam/mjpg-streamer) при использовании им плагинов ```input_uvc.so``` и ```output_http.so```, однако имеет ряд серьезных отличий. Основные приведены в этой таблице:
| **Фича** | **µStreamer** | **mjpg-streamer** |
|----------|---------------|-------------------|
| Многопоточное кодирование JPEG | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Аппаратное кодирование с помощью [OpenMAX IL](https://www.khronos.org/openmaxil) на Raspberry Pi | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Поведение при физическом отключении<br>устройства от сервера во время работы | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Транслирует черный экран<br>с надписью ```NO SIGNAL```,<br>пока устройство не будет подключено снова | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Прерывает трансляцию <sup>1</sup> |
| Поддержка [DV-таймингов](https://linuxtv.org/downloads/v4l-dvb-apis-new/userspace-api/v4l/dv-timings.html) - возможности<br>изменения параметров разрешения<br>трансляции на лету по сигналу<br>источника (устройства видеозахвата) | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) Условно есть <sup>1</sup> |
| Возможность пропуска фреймов при передаче<br>статического изображения по HTTP<br>для экономии трафика | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть <sup>2</sup> | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Стрим через UNIX domain socket | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Дебаг-логи без перекомпиляции,<br>логгирование статистики производительности,<br>возможность получения параметров<br>трансляции по HTTP | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Возможность сервить файлы встроенным<br>HTTP-сервером | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) Нет каталогов |
| Вывод сигналов о состоянии стрима на GPIO<br>с помощью [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Поддержка контролов веб-камер (фокус,<br> движение сервами) и всяких настроек,<br> типа яркости, через HTTP | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть |
| Совместимость с API mjpg-streamer'а | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | :) |
| Многопоточное кодирование JPEG | ✔ | ✘ |
| Аппаратное кодирование на Raspberry Pi | ✔ | ✘ |
| Поведение при физическом отключении<br>устройства от сервера во время работы | Транслирует черный экран<br>с надписью ```NO SIGNAL```,<br>пока устройство не будет подключено снова | Прерывает трансляцию <sup>1</sup> |
| Поддержка [DV-таймингов](https://linuxtv.org/downloads/v4l-dvb-apis-new/userspace-api/v4l/dv-timings.html) - возможности<br>изменения параметров разрешения<br>трансляции на лету по сигналу<br>источника (устройства видеозахвата) | ✔ | ☹ Условно есть <sup>1</sup> |
| Возможность пропуска фреймов при передаче<br>статического изображения по HTTP<br>для экономии трафика | ✔ <sup>2</sup> | ✘ |
| Стрим через UNIX domain socket | ✔ | ✘ |
| Systemd socket activation | ✔ | ✘ |
| Дебаг-логи без перекомпиляции,<br>логгирование статистики производительности,<br>возможность получения параметров<br>трансляции по HTTP | ✔ | ✘ |
| Возможность сервить файлы встроенным<br>HTTP-сервером | ✔ | ☹ Нет каталогов |
| Вывод сигналов о состоянии стрима на GPIO<br>с помощью [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) | ✔ | ✘ |
| Поддержка контролов веб-камер (фокус,<br> движение сервами) и всяких настроек,<br> типа яркости, через HTTP | ✘ | |
| Совместимость с API mjpg-streamer'а | ✔ | :) |
Сносочки:
* ```1``` Еще до написания µStreamer, я запилил [патч](https://github.com/jacksonliam/mjpg-streamer/pull/164), добавляющий в mjpg-streamer поддержку DV-таймингов и предотвращающий его зависание при отключении устройства. Однако патч, увы, далек от совершенства и я не гарантирую его стопроцентную работоспособность, поскольку код mjpg-streamer чрезвычайно запутан и очень плохо структурирован. Учитывая это, а также то, что в дальнейшем мне потребовались многопоточность и аппаратное кодирование JPEG, было принято решение написать свой стрим-сервер с нуля, чтобы не тратить силы на поддержку лишнего легаси.
* ```2``` Это фича позволяет в несколько раз снизить объем исходящего трафика при трансляции HDMI, однако немного увеличивает загрузку процессора. Суть в том, что HDMI - полностью цифровой интерфейс, и новый захваченный фрейм может быть идентичен предыдущему в точности до байта. В этом случае нет нужды передавать одну и ту же картинку по сети несколько раз в секунду. При использовании опции `--drop-same-frames=20`, µStreamer будет дропать все одинаковые фреймы, но не более 20 подряд. Новый фрейм сравнивается с предыдущим сначала по длине, а затем помощью ```memcmp()```.
-----
@@ -34,14 +35,13 @@
-----
# Сборка
Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads```, ```libjpeg8```/```libjpeg-turbo```, ```libuuid``` и ```libbsd``` (только для Linux).
Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads```, ```libjpeg8```/```libjpeg-turbo``` и ```libbsd``` (только для Linux).
* Arch: `sudo pacman -S libevent libjpeg-turbo libutil-linux libbsd`.
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev uuid-dev libbsd-dev`. Добавьте `libraspberrypi-dev` для сборки с `WITH_OMX=1` и `libgpiod` для `WITH_GPIO=1`.
* Debian: `sudo apt install build-essential libevent-dev libjpeg62-turbo-dev uuid-dev libbsd-dev`.
* Ubuntu 20.04 x86_64: `sudo apt install build-essential libevent-dev libjpeg62-dev uuid-dev libbsd-dev make gcc libjpeg8 libjpeg-turbo8 libuuid1 libbsd0`.
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev libbsd-dev`. Добавьте `libgpiod` для `WITH_GPIO=1` и `libsystemd-dev` для `WITH_SYSTEMD=1`.
* Debian/Ubuntu: `sudo apt install build-essential libevent-dev libjpeg-dev libbsd-dev`.
На Raspberry Pi программу можно собрать с поддержкой OpenMAX IL. Для этого передайте ```make``` параметр ```WITH_OMX=1```. Для включения сборки с поддержкой GPIO установите [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) и добавьте параметр ```WITH_GPIO=1```. Если при сборке компилятор ругается на отсутствие функции ```pthread_get_name_np()``` или другой подобной, добавьте параметр ```WITH_PTHREAD_NP=0``` (по умолчанию он включен). При аналогичной ошибке с функцией ```setproctitle()``` добавьте параметр ```WITH_SETPROCTITLE=0```.
Для включения сборки с поддержкой GPIO установите [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) и добавьте параметр ```WITH_GPIO=1```. Если при сборке компилятор ругается на отсутствие функции ```pthread_get_name_np()``` или другой подобной, добавьте параметр ```WITH_PTHREAD_NP=0``` (по умолчанию он включен). При аналогичной ошибке с функцией ```setproctitle()``` добавьте параметр ```WITH_SETPROCTITLE=0```.
```
$ git clone --depth=1 https://github.com/pikvm/ustreamer
@@ -50,11 +50,14 @@ $ make
$ ./ustreamer --help
```
Для Arch Linux в AUR есть готовый пакет: https://aur.archlinux.org/packages/ustreamer. На Raspberry Pi програма автоматически собирается с поддержкой OpenMAX IL, если обнаружит нужные хедеры в ```/opt/vc/include```.
Для Arch Linux в AUR есть готовый пакет: https://aur.archlinux.org/packages/ustreamer.
Порт для FreeBSD: https://www.freshports.org/multimedia/ustreamer.
-----
# Использование
**Для аппаратного кодирования M2M на Raspberry Pi, вам нужно ядро минимальной версии 5.15.32. Поддержка OpenMAX и MMAL для более старых ядер объявлена устаревшей и была удалена.**
Будучи запущенным без аргументов, ```ustreamer``` попробует открыть устройство ```/dev/video0``` с разрешением 640x480 и начать трансляцию на ```http://127.0.0.1:8080```. Это поведение может быть изменено с помощью опций ```--device```, ```--host``` и ```--port```. Пример вещания на всю сеть по 80-м порту:
```
# ./ustreamer --device=/dev/video1 --host=0.0.0.0 --port=80
@@ -66,8 +69,8 @@ $ ./ustreamer --help
```bash
$ ./ustreamer \
--format=uyvy \ # Настройка входного формата устройства
--encoder=omx \ # Использование аппаратного кодирования с помощью OpenMAX
--workers=3 \ # Максимум воркеров для OpenMAX
--encoder=m2m-image \ # Аппаратное кодирование с помощью драйвера V4L2 M2M
--workers=3 \ # Максимум воркеров
--persistent \ # Не переинициализировать устройство при таймауте (например, когда был отключен HDMI-кабель)
--dv-timings \ # Включение DV-таймингов
--drop-same-frames=30 # Экономим трафик
@@ -77,14 +80,56 @@ $ ./ustreamer \
За полным списком опций обращайтесь ко встроенной справке: ```ustreamer --help```.
-----
# Камера Raspberry Pi
Пример использования камеры Raspberry Pi v1:
```bash
$ sudo modprobe bcm2835-v4l2
$ ./ustreamer --host :: -m jpeg --device-timeout=5 --buffers=3 -r 2592x1944
```
:exclamation: Обратите внимание что боле новые модели камеры имеют другое максимальное разрешение. Список поддерживаемых разрешений можно найти в [документации PiCamera](https://picamera.readthedocs.io/en/release-1.13/fov.html#sensor-modes).
:exclamation: Если камера выдает низкий фреймрейт, возможно что она работает в фото-режиме, где производит более низкий фпс, но более качественную кратинку. Это происходит потому что `bcm2835-v4l2` переключает камеру в фото-режим на разрешениях выше `1280x720`. Чтобы обойти это, передайте параметры `max_video_width` и `max_video_height` при загрузке модуля, например:
```bash
$ modprobe bcm2835-v4l2 max_video_width=2592 max_video_height=1944
```
-----
# Интеграция
## Nginx
Если uStreamer находится на Nginx, то последний будет буферизировать поток и создавать дополнительную задержку в стриме. Чтобы задержки не было, буферизацию можно отключить:
```nginx
location /stream {
postpone_output 0;
proxy_buffering off;
proxy_ignore_headers X-Accel-Buffering;
proxy_pass http://ustreamer;
}
```
-----
# Утилиты V4L2
V4L2 предоставляет ряд официальных утилит для управления USB-вебкамерами и получения информации об устройствах. С их помощью можно писать всякие настроечные скрипты и запускать их по крону, если, например, вам требуется изменять настройки экспозиции в зависимости от времени суток. Пакет с этими утилитами доступен на всех дистрибутивах Linux и обычно называется `v4l-utils`.
* Вывести список видеоустройств: `v4l2-ctl --list-devices`.
* Вывести список доступных контролов устройства: `v4l2-ctl -d /dev/video0 --list-ctrls`.
* Вывести список доступных форматов видео: `v4l2-ctl -d /dev/video0 --list-formats-ext`.
* Показать текущее значение контрола: `v4l2-ctl -d /dev/video0 --get-ctrl=exposure_auto`.
* Изменить значение контрола: `v4l2-ctl -d /dev/video0 --set-ctrl=exposure_auto=1`.
Больше примеров вы можете найти [здесь](https://www.kurokesu.com/main/2016/01/16/manual-usb-camera-settings-in-linux/), а документацию в [`man v4l2-ctl`](https://www.mankier.com/1/v4l2-ctl).
-----
# Смотрите также
* [Запуск с помощью systemd-сервиса](https://github.com/pikvm/ustreamer/issues/16).
* [uStreamer Ansible Role](https://github.com/mtlynch/ansible-role-ustreamer): Использование [Ansible](https://docs.ansible.com/ansible/latest/index.html) для сборки и установки стримера как systemd-сервиса.
-----
# Лицензия
Copyright (C) 2018-2021 by Maxim Devaev mdevaev@gmail.com
Copyright (C) 2018-2022 by Maxim Devaev mdevaev@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

231
docs/h264.md Normal file
View File

@@ -0,0 +1,231 @@
# Streaming H.264 Video over WebRTC with Janus
µStreamer supports bandwidth-efficient streaming using [H.264 compression](https://en.wikipedia.org/wiki/Advanced_Video_Coding) and the Janus WebRTC server.
This guide explains how to configure µStreamer to provide an H.264 video stream and consume it within a web application using WebRTC.
## Components
In addition to µStreamer, H.264 streaming involves the following components:
- [**Janus**](https://janus.conf.meetecho.com/): A general-purpose WebRTC server.
- [**µStreamer Janus Plugin**](https://github.com/pikvm/ustreamer/tree/master/janus): A Janus plugin that allows Janus to consume a video stream from µStreamer via shared memory.
- [**Janus JavaScript Client**](https://janus.conf.meetecho.com/docs/JS.html): A frontend library that communicates with the Janus server and the µStreamer Janus plugin.
## Control Flow
To connect to a µStreamer video stream over WebRTC, an application must establish the following control flow:
1. The backend server starts the µStreamer service.
1. The backend server starts the Janus WebRTC server with the µStreamer Janus plugin.
1. The client-side JavaScript application establishes a connection to the Janus server.
1. The client-side JavaScript application instructs the Janus server to attach the µStreamer Janus plugin and start the video stream.
1. The client-side JavaScript application renders the video stream in the web browser.
## Server Setup
### Prerequisites
To integrate with Janus, µStreamer has the following prerequisites:
- The system packages that µStreamer depends on (see the [main build instructions](../README.md#building)).
- The Janus WebRTC server with WebSockets transport enabled (see the [Janus documentation](https://github.com/meetecho/janus-gateway)).
### Fixing Janus C Headers
To compile µStreamer with the Janus option, (see [“Installation”](#installation)), you need to make the Janus header files available within µStreamer's build context.
First, create a symbolic link from `/usr/include` to the Janus install directory. By default, Janus installs to `/opt/janus`.
```sh
ln -s /opt/janus/include/janus /usr/include/janus
```
Janus uses `#include` paths that make it difficult for third-party libraries to build against its headers. To fix this, modify the `#include` directive in `janus/plugins/plugin.h` to prepend a `../` to the included file name (`#include "refcount.h"``#include "../refcount.h"`).
```sh
sed \
--in-place \
--expression 's|^#include "refcount.h"$|#include "../refcount.h"|g' \
/usr/include/janus/plugins/plugin.h
```
### Installation
First, compile µStreamer with the Janus plugin option (`WITH_JANUS`).
```sh
git clone --depth=1 https://github.com/pikvm/ustreamer
cd ustreamer
make WITH_JANUS=1
```
The `WITH_JANUS` option compiles the µStreamer Janus plugin and outputs it to `janus/libjanus_ustreamer.so`. Move this file to the plugin directory of your Janus installation.
```sh
mv \
janus/libjanus_ustreamer.so \
/opt/janus/lib/janus/plugins/libjanus_ustreamer.so
```
Finally, specify a qualifier for the shared memory object so that the µStreamer Janus plugin can read µStreamer's video data.
```sh
cat > /opt/janus/lib/janus/configs/janus.plugin.ustreamer.jcfg <<EOF
memsink: {
object = "demo::ustreamer::h264"
}
EOF
```
### Start µStreamer and the Janus WebRTC Server
For µStreamer to share the video stream with the µStreamer Janus plugin, µStreamer must run with the following command-line flags:
- `--h264-sink` with the qualifier of the shared memory object you specified above (`demo::ustreamer::h264`)
- `--h264-sink-mode` with the permissions bitmask for the shared memory object (e.g., `660`)
- `--h264-sink-rm` to clean up the shared memory object when the µStreamer process exits
To load the µStreamer Janus plugin and configuration, the Janus WebRTC server must run with the following command-line flags:
- `--configs-folder` with the path to the Janus configuration directory (e.g., `/opt/janus/lib/janus/configs/`)
- `--plugins-folder` with the path to the Janus plugin directory (e.g., `/opt/janus/lib/janus/plugins/`)
## Client Setup
Once an application's backend server is running µStreamer and Janus, a browser-based JavaScript application can consume the server's video stream.
### Prerequisites
The client needs to load the following JavaScript libraries:
- [WebRTC Adapter library](https://webrtc.github.io/adapter/adapter-8.1.0.js) (`webrtc-adapter.js`, version `8.1.0`)
- [Janus Gateway JavaScript library](https://raw.githubusercontent.com/meetecho/janus-gateway/v1.0.0/html/janus.js) (`janus.js`, version `1.0.0`)
### Control Flow
The client-side JavaScript application uses the following control flow:
1. The client loads and initializes the Janus client library.
1. The client establishes a WebSockets connection to the Janus server.
1. The client instructs the Janus server to attach the µStreamer Janus plugin.
1. On success, the client obtains a plugin handle through which it can send requests directly to the µStreamer Janus plugin. The client processes responses via the `attach` callbacks:
- `onmessage` for general messages
- `onremotetrack` for the H.264 video stream
1. The client issues a `watch` request to the µStreamer Janus plugin, which initiates the H.264 stream in the plugin itself.
- It takes a few seconds for uStreamer's video stream to become available to Janus. The first `watch` request may fail, so the client must retry the `watch` request.
1. The client and server negotiate the underlying parameters of the WebRTC session. This procedure is called JavaScript Session Establishment Protocol (JSEP). The server makes a `jsepOffer` to the client, and the client responds with a `jsepAnswer`.
1. The client issues a `start` request to the µStreamer Janus plugin to indicate that the client wants to begin consuming the video stream.
1. The µStreamer Janus plugin delivers the H.264 video stream to the client via WebRTC.
1. The Janus client library invokes the `onremotetrack` callback. The client attaches the video stream to the `<video>` element, rendering the video stream in the browser window.
### Sample Code
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>µStreamer H.264 demo</title>
<script src="https://webrtc.github.io/adapter/adapter-8.1.0.js"></script>
<!-- janus.js is the JavaScript client library of Janus, as specified above in
the prerequisites section of the client setup. You might need to change
the `src` path, depending on where you serve this file from. -->
<script src="janus.js"></script>
<style>
video {
height: 100%;
width: 100%;
object-fit: contain;
}
</style>
</head>
<body>
<video id="webrtc-output" autoplay playsinline muted></video>
<script type="text/javascript">
// Initialize Janus library.
Janus.init({
// Turn on debug logs in the browser console.
debug: true,
// Configure Janus to use standard browser APIs internally.
dependencies: Janus.useDefaultDependencies(),
});
// Establish a WebSockets connection to the server.
const janus = new Janus({
// Specify the URL of the Janus servers WebSockets endpoint.
server: `ws://${window.location.hostname}:8188/`,
// Callback function if the client connects successfully.
success: attachUStreamerPlugin,
// Callback function if the client fails to connect.
error: console.error,
});
let uStreamerPluginHandle = null;
function attachUStreamerPlugin() {
// Instruct the server to attach the µStreamer Janus plugin.
janus.attach({
// Qualifier of the plugin.
plugin: "janus.plugin.ustreamer",
// Callback function, for when the server attached the plugin
// successfully.
success: function (pluginHandle) {
uStreamerPluginHandle = pluginHandle;
// Instruct the µStreamer Janus plugin to initiate the video stream.
uStreamerPluginHandle.send({ message: { request: "watch" } });
},
// Callback function if the server fails to attach the plugin.
error: console.error,
// Callback function for processing messages from the Janus server.
onmessage: function (msg, jsepOffer) {
// 503 indicates that the plugin is not ready to stream yet. Retry the
// watch request until the video stream is available.
if (msg.error_code === 503) {
uStreamerPluginHandle.send({ message: { request: "watch" } });
return;
}
// If there is a JSEP offer, respond to it. This starts the WebRTC
// connection.
if (jsepOffer) {
uStreamerPluginHandle.createAnswer({
jsep: jsepOffer,
// Prevent the client from sending audio and video, as this would
// trigger a permission dialog in the browser.
media: { audioSend: false, videoSend: false },
success: function (jsepAnswer) {
uStreamerPluginHandle.send({
message: { request: "start" },
jsep: jsepAnswer,
});
},
error: console.error,
});
}
},
// Callback function, for when the video stream arrives.
onremotetrack: function (mediaStreamTrack, mediaId, isAdded) {
if (isAdded) {
// Attach the received media track to the video element. Cloning the
// mediaStreamTrack creates a new object with a distinct, globally
// unique stream identifier.
const videoElement = document.getElementById("webrtc-output");
const stream = new MediaStream();
stream.addTrack(mediaStreamTrack.clone());
videoElement.srcObject = stream;
}
},
});
}
</script>
</body>
</html>
```

54
janus/Makefile Normal file
View File

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

242
janus/src/audio.c Normal file
View File

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

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

@@ -0,0 +1,69 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdatomic.h>
#include <assert.h>
#include <sys/types.h>
#include <pthread.h>
#include <alsa/asoundlib.h>
#include <speex/speex_resampler.h>
#include <opus/opus.h>
#include "uslibs/tools.h"
#include "uslibs/array.h"
#include "uslibs/threading.h"
#include "logging.h"
#include "queue.h"
typedef struct {
snd_pcm_t *pcm;
unsigned pcm_hz;
unsigned pcm_frames;
size_t pcm_size;
snd_pcm_hw_params_t *pcm_params;
SpeexResamplerState *res;
OpusEncoder *enc;
us_queue_s *pcm_queue;
us_queue_s *enc_queue;
uint32_t pts;
pthread_t pcm_tid;
pthread_t enc_tid;
bool tids_created;
atomic_bool stop;
} us_audio_s;
us_audio_s *us_audio_init(const char *name, unsigned pcm_hz);
void us_audio_destroy(us_audio_s *audio);
int us_audio_get_encoded(us_audio_s *audio, uint8_t *data, size_t *size, uint64_t *pts);

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

@@ -0,0 +1,119 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "client.h"
static void *_video_thread(void *v_client);
static void *_audio_thread(void *v_client);
static void *_common_thread(void *v_client, bool video);
us_janus_client_s *us_janus_client_init(janus_callbacks *gw, janus_plugin_session *session, bool has_audio) {
us_janus_client_s *client;
US_CALLOC(client, 1);
client->gw = gw;
client->session = session;
atomic_init(&client->transmit, true);
atomic_init(&client->stop, false);
client->video_queue = us_queue_init(1024);
US_THREAD_CREATE(client->video_tid, _video_thread, client);
if (has_audio) {
client->audio_queue = us_queue_init(64);
US_THREAD_CREATE(client->audio_tid, _audio_thread, client);
}
return client;
}
void us_janus_client_destroy(us_janus_client_s *client) {
atomic_store(&client->stop, true);
us_queue_put(client->video_queue, NULL, 0);
if (client->audio_queue != NULL) {
us_queue_put(client->audio_queue, NULL, 0);
}
US_THREAD_JOIN(client->video_tid);
US_QUEUE_DELETE_WITH_ITEMS(client->video_queue, us_rtp_destroy);
if (client->audio_queue != NULL) {
US_THREAD_JOIN(client->audio_tid);
US_QUEUE_DELETE_WITH_ITEMS(client->audio_queue, us_rtp_destroy);
}
free(client);
}
void us_janus_client_send(us_janus_client_s *client, const us_rtp_s *rtp) {
if (
!atomic_load(&client->transmit)
|| (!rtp->video && client->audio_queue == NULL)
) {
return;
}
us_rtp_s *const new = us_rtp_dup(rtp);
if (us_queue_put((new->video ? client->video_queue : client->audio_queue), new, 0) != 0) {
US_JLOG_ERROR("client", "Session %p %s queue is full",
client->session, (new->video ? "video" : "audio"));
us_rtp_destroy(new);
}
}
static void *_video_thread(void *v_client) {
return _common_thread(v_client, true);
}
static void *_audio_thread(void *v_client) {
return _common_thread(v_client, false);
}
static void *_common_thread(void *v_client, bool video) {
us_janus_client_s *const client = (us_janus_client_s *)v_client;
us_queue_s *const queue = (video ? client->video_queue : client->audio_queue);
assert(queue != NULL); // Audio may be NULL
while (!atomic_load(&client->stop)) {
us_rtp_s *rtp;
if (!us_queue_get(queue, (void **)&rtp, 0.1)) {
if (rtp == NULL) {
break;
}
if (atomic_load(&client->transmit)) {
janus_plugin_rtp packet = {0};
packet.video = rtp->video;
packet.buffer = (char *)rtp->datagram;
packet.length = rtp->used;
janus_plugin_rtp_extensions_reset(&packet.extensions);
// FIXME: Это очень эффективный способ уменьшить задержку, но WebRTC стек в хроме и фоксе
// слишком корявый, чтобы обработать это, из-за чего на кейфреймах начинаются заикания.
// - https://github.com/Glimesh/janus-ftl-plugin/issues/101
if (rtp->zero_playout_delay) {
packet.extensions.min_delay = 0;
packet.extensions.max_delay = 0;
}
client->gw->relay_rtp(client->session, &packet);
}
us_rtp_destroy(rtp);
}
}
return NULL;
}

View File

@@ -1,8 +1,8 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -22,32 +22,40 @@
#pragma once
#include <stdlib.h>
#include <stdbool.h>
#include <stdatomic.h>
#include <string.h>
#include <unistd.h>
#include <IL/OMX_Core.h>
#include <IL/OMX_Component.h>
#include <pthread.h>
#include <janus/plugins/plugin.h>
#include "../../../libs/logging.h"
#include "uslibs/tools.h"
#include "uslibs/threading.h"
#include "uslibs/list.h"
#include "formatters.h"
#include "logging.h"
#include "queue.h"
#include "rtp.h"
#define OMX_INIT_STRUCTURE(_var) { \
memset(&(_var), 0, sizeof(_var)); \
(_var).nSize = sizeof(_var); \
(_var).nVersion.nVersion = OMX_VERSION; \
(_var).nVersion.s.nVersionMajor = OMX_VERSION_MAJOR; \
(_var).nVersion.s.nVersionMinor = OMX_VERSION_MINOR; \
(_var).nVersion.s.nRevision = OMX_VERSION_REVISION; \
(_var).nVersion.s.nStep = OMX_VERSION_STEP; \
}
typedef struct us_janus_client_sx {
janus_callbacks *gw;
janus_plugin_session *session;
atomic_bool transmit;
pthread_t video_tid;
pthread_t audio_tid;
atomic_bool stop;
us_queue_s *video_queue;
us_queue_s *audio_queue;
US_LIST_STRUCT(struct us_janus_client_sx);
} us_janus_client_s;
int omx_component_enable_port(OMX_HANDLETYPE *comp, OMX_U32 port);
int omx_component_disable_port(OMX_HANDLETYPE *comp, OMX_U32 port);
us_janus_client_s *us_janus_client_init(janus_callbacks *gw, janus_plugin_session *session, bool has_audio);
void us_janus_client_destroy(us_janus_client_s *client);
int omx_component_get_portdef(OMX_HANDLETYPE *comp, OMX_PARAM_PORTDEFINITIONTYPE *portdef, OMX_U32 port);
int omx_component_set_portdef(OMX_HANDLETYPE *comp, OMX_PARAM_PORTDEFINITIONTYPE *portdef);
int omx_component_set_state(OMX_HANDLETYPE *comp, OMX_STATETYPE state);
void us_janus_client_send(us_janus_client_s *client, const us_rtp_s *rtp);

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

@@ -0,0 +1,99 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "config.h"
static char *_get_value(janus_config *jcfg, const char *section, const char *option);
static bool _get_bool(janus_config *jcfg, const char *section, const char *option, bool def);
us_config_s *us_config_init(const char *config_dir_path) {
us_config_s *config;
US_CALLOC(config, 1);
char *config_file_path;
janus_config *jcfg = NULL;
US_ASPRINTF(config_file_path, "%s/%s.jcfg", config_dir_path, US_PLUGIN_PACKAGE);
US_JLOG_INFO("config", "Reading config file '%s' ...", config_file_path);
jcfg = janus_config_parse(config_file_path);
if (jcfg == NULL) {
US_JLOG_ERROR("config", "Can't read config");
goto error;
}
janus_config_print(jcfg);
if (
(config->video_sink_name = _get_value(jcfg, "memsink", "object")) == NULL
&& (config->video_sink_name = _get_value(jcfg, "video", "sink")) == NULL
) {
US_JLOG_ERROR("config", "Missing config value: video.sink (ex. memsink.object)");
goto error;
}
if ((config->video_zero_playout_delay = _get_bool(jcfg, "video", "zero_playout_delay", false)) == true) {
US_JLOG_INFO("config", "Enabled the experimental Playout-Delay=0 RTP extension for VIDEO");
}
if ((config->audio_dev_name = _get_value(jcfg, "audio", "device")) != NULL) {
US_JLOG_INFO("config", "Enabled the experimental AUDIO feature");
if ((config->tc358743_dev_path = _get_value(jcfg, "audio", "tc358743")) == NULL) {
US_JLOG_INFO("config", "Missing config value: audio.tc358743");
goto error;
}
}
goto ok;
error:
us_config_destroy(config);
config = NULL;
ok:
US_DELETE(jcfg, janus_config_destroy);
free(config_file_path);
return config;
}
void us_config_destroy(us_config_s *config) {
US_DELETE(config->video_sink_name, free);
US_DELETE(config->audio_dev_name, free);
US_DELETE(config->tc358743_dev_path, free);
free(config);
}
static char *_get_value(janus_config *jcfg, const char *section, const char *option) {
janus_config_category *section_obj = janus_config_get_create(jcfg, NULL, janus_config_type_category, section);
janus_config_item *option_obj = janus_config_get(jcfg, section_obj, janus_config_type_item, option);
if (option_obj == NULL || option_obj->value == NULL || option_obj->value[0] == '\0') {
return NULL;
}
return us_strdup(option_obj->value);
}
static bool _get_bool(janus_config *jcfg, const char *section, const char *option, bool def) {
char *const tmp = _get_value(jcfg, section, option);
bool value = def;
if (tmp != NULL) {
value = (!strcasecmp(tmp, "1") || !strcasecmp(tmp, "true") || !strcasecmp(tmp, "yes"));
free(tmp);
}
return value;
}

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

@@ -0,0 +1,47 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <stdlib.h>
#include <string.h>
#include <janus/config.h>
#include <janus/plugins/plugin.h>
#include "uslibs/tools.h"
#include "const.h"
#include "logging.h"
typedef struct {
char *video_sink_name;
bool video_zero_playout_delay;
char *audio_dev_name;
char *tc358743_dev_path;
} us_config_s;
us_config_s *us_config_init(const char *config_dir_path);
void us_config_destroy(us_config_s *config);

View File

@@ -1,8 +1,8 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -22,6 +22,5 @@
#pragma once
#ifndef VERSION
# define VERSION "3.3"
#endif
#define US_PLUGIN_NAME "ustreamer"
#define US_PLUGIN_PACKAGE "janus.plugin.ustreamer"

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

@@ -0,0 +1,38 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include <janus/plugins/plugin.h>
#include "uslibs/tools.h"
#include "const.h"
#define US_JLOG_INFO(x_prefix, x_msg, ...) JANUS_LOG(LOG_INFO, "== %s/%-9s -- " x_msg "\n", US_PLUGIN_NAME, x_prefix, ##__VA_ARGS__)
#define US_JLOG_WARN(x_prefix, x_msg, ...) JANUS_LOG(LOG_WARN, "== %s/%-9s -- " x_msg "\n", US_PLUGIN_NAME, x_prefix, ##__VA_ARGS__)
#define US_JLOG_ERROR(x_prefix, x_msg, ...) JANUS_LOG(LOG_ERR, "== %s/%-9s -- " x_msg "\n", US_PLUGIN_NAME, x_prefix, ##__VA_ARGS__)
#define US_JLOG_PERROR(x_prefix, x_msg, ...) { \
char *const m_perror_str = us_errno_to_string(errno); \
JANUS_LOG(LOG_ERR, "[%s/%-9s] " x_msg ": %s\n", US_PLUGIN_NAME, x_prefix, ##__VA_ARGS__, m_perror_str); \
free(m_perror_str); \
}

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

@@ -0,0 +1,70 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "memsinkfd.h"
int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s* mem, uint64_t last_id) {
const long double deadline_ts = us_get_now_monotonic() + 1; // wait_timeout
long double now;
do {
const int result = us_flock_timedwait_monotonic(fd, 1); // lock_timeout
now = us_get_now_monotonic();
if (result < 0 && errno != EWOULDBLOCK) {
US_JLOG_PERROR("video", "Can't lock memsink");
return -1;
} else if (result == 0) {
if (mem->magic == US_MEMSINK_MAGIC && mem->version == US_MEMSINK_VERSION && mem->id != last_id) {
return 0;
}
if (flock(fd, LOCK_UN) < 0) {
US_JLOG_PERROR("video", "Can't unlock memsink");
return -1;
}
}
usleep(1000); // lock_polling
} while (now < deadline_ts);
return -2;
}
us_frame_s *us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, uint64_t *frame_id) {
us_frame_s *frame = us_frame_init();
us_frame_set_data(frame, mem->data, mem->used);
US_FRAME_COPY_META(mem, frame);
*frame_id = mem->id;
mem->last_client_ts = us_get_now_monotonic();
bool ok = true;
if (frame->format != V4L2_PIX_FMT_H264) {
US_JLOG_ERROR("video", "Got non-H264 frame from memsink");
ok = false;
}
if (flock(fd, LOCK_UN) < 0) {
US_JLOG_PERROR("video", "Can't unlock memsink");
ok = false;
}
if (!ok) {
us_frame_destroy(frame);
frame = NULL;
}
return frame;
}

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

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

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

@@ -0,0 +1,504 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include <stdint.h>
#include <stdbool.h>
#include <stdatomic.h>
#include <stdlib.h>
#include <inttypes.h>
#include <unistd.h>
#include <fcntl.h>
#include <assert.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <pthread.h>
#include <jansson.h>
#include <janus/plugins/plugin.h>
#include "uslibs/const.h"
#include "uslibs/tools.h"
#include "uslibs/threading.h"
#include "uslibs/list.h"
#include "uslibs/memsinksh.h"
#include "const.h"
#include "logging.h"
#include "queue.h"
#include "client.h"
#include "audio.h"
#include "tc358743.h"
#include "rtp.h"
#include "rtpv.h"
#include "rtpa.h"
#include "memsinkfd.h"
#include "config.h"
static us_config_s *_g_config = NULL;
const useconds_t _g_watchers_polling = 100000;
static us_janus_client_s *_g_clients = NULL;
static janus_callbacks *_g_gw = NULL;
static us_queue_s *_g_video_queue = NULL;
static us_rtpv_s *_g_rtpv = NULL;
static us_rtpa_s *_g_rtpa = NULL;
static pthread_t _g_video_rtp_tid;
static atomic_bool _g_video_rtp_tid_created = false;
static pthread_t _g_video_sink_tid;
static atomic_bool _g_video_sink_tid_created = false;
static pthread_t _g_audio_tid;
static atomic_bool _g_audio_tid_created = false;
static pthread_mutex_t _g_video_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t _g_audio_lock = PTHREAD_MUTEX_INITIALIZER;
static atomic_bool _g_ready = false;
static atomic_bool _g_stop = false;
static atomic_bool _g_has_watchers = false;
#define _LOCK_VIDEO US_MUTEX_LOCK(_g_video_lock)
#define _UNLOCK_VIDEO US_MUTEX_UNLOCK(_g_video_lock)
#define _LOCK_AUDIO US_MUTEX_LOCK(_g_audio_lock)
#define _UNLOCK_AUDIO US_MUTEX_UNLOCK(_g_audio_lock)
#define _LOCK_ALL { _LOCK_VIDEO; _LOCK_AUDIO; }
#define _UNLOCK_ALL { _UNLOCK_AUDIO; _UNLOCK_VIDEO; }
#define _READY atomic_load(&_g_ready)
#define _STOP atomic_load(&_g_stop)
#define _HAS_WATCHERS atomic_load(&_g_has_watchers)
janus_plugin *create(void);
#define _IF_NOT_REPORTED(...) { \
const unsigned _error_code = __LINE__; \
if (error_reported != _error_code) { __VA_ARGS__; error_reported = _error_code; } \
}
static void *_video_rtp_thread(UNUSED void *arg) {
US_THREAD_RENAME("us_video_rtp");
atomic_store(&_g_video_rtp_tid_created, true);
while (!_STOP) {
us_frame_s *frame;
if (us_queue_get(_g_video_queue, (void **)&frame, 0.1) == 0) {
_LOCK_VIDEO;
us_rtpv_wrap(_g_rtpv, frame);
_UNLOCK_VIDEO;
us_frame_destroy(frame);
}
}
return NULL;
}
static void *_video_sink_thread(UNUSED void *arg) {
US_THREAD_RENAME("us_video_sink");
atomic_store(&_g_video_sink_tid_created, true);
uint64_t frame_id = 0;
unsigned error_reported = 0;
while (!_STOP) {
if (!_HAS_WATCHERS) {
_IF_NOT_REPORTED({ US_JLOG_INFO("video", "No active watchers, memsink disconnected"); });
usleep(_g_watchers_polling);
continue;
}
int fd = -1;
us_memsink_shared_s *mem = NULL;
if ((fd = shm_open(_g_config->video_sink_name, O_RDWR, 0)) <= 0) {
_IF_NOT_REPORTED({ US_JLOG_PERROR("video", "Can't open memsink"); });
goto close_memsink;
}
if ((mem = us_memsink_shared_map(fd)) == NULL) {
_IF_NOT_REPORTED({ US_JLOG_PERROR("video", "Can't map memsink"); });
goto close_memsink;
}
error_reported = 0;
US_JLOG_INFO("video", "Memsink opened; reading frames ...");
while (!_STOP && _HAS_WATCHERS) {
const int result = us_memsink_fd_wait_frame(fd, mem, frame_id);
if (result == 0) {
us_frame_s *const frame = us_memsink_fd_get_frame(fd, mem, &frame_id);
if (frame == NULL) {
goto close_memsink;
}
if (us_queue_put(_g_video_queue, frame, 0) != 0) {
_IF_NOT_REPORTED({ US_JLOG_PERROR("video", "Video queue is full"); });
us_frame_destroy(frame);
}
} else if (result == -1) {
goto close_memsink;
}
}
close_memsink:
if (mem != NULL) {
US_JLOG_INFO("video", "Memsink closed");
us_memsink_shared_unmap(mem);
}
if (fd >= 0) {
close(fd);
}
sleep(1); // error_delay
}
return NULL;
}
static void *_audio_thread(UNUSED void *arg) {
US_THREAD_RENAME("us_audio");
atomic_store(&_g_audio_tid_created, true);
assert(_g_config->audio_dev_name != NULL);
assert(_g_config->tc358743_dev_path != NULL);
unsigned error_reported = 0;
while (!_STOP) {
if (!_HAS_WATCHERS) {
usleep(_g_watchers_polling);
continue;
}
us_tc358743_info_s info = {0};
us_audio_s *audio = NULL;
if (us_tc358743_read_info(_g_config->tc358743_dev_path, &info) < 0) {
goto close_audio;
}
if (!info.has_audio) {
_IF_NOT_REPORTED({ US_JLOG_INFO("audio", "No audio presented from the host"); });
goto close_audio;
}
_IF_NOT_REPORTED({ US_JLOG_INFO("audio", "Detected host audio"); });
if ((audio = us_audio_init(_g_config->audio_dev_name, info.audio_hz)) == NULL) {
goto close_audio;
}
error_reported = 0;
while (!_STOP && _HAS_WATCHERS) {
if (
us_tc358743_read_info(_g_config->tc358743_dev_path, &info) < 0
|| !info.has_audio
|| audio->pcm_hz != info.audio_hz
) {
goto close_audio;
}
size_t size = US_RTP_DATAGRAM_SIZE - US_RTP_HEADER_SIZE;
uint8_t data[size];
uint64_t pts;
const int result = us_audio_get_encoded(audio, data, &size, &pts);
if (result == 0) {
_LOCK_AUDIO;
us_rtpa_wrap(_g_rtpa, data, size, pts);
_UNLOCK_AUDIO;
} else if (result == -1) {
goto close_audio;
}
}
close_audio:
US_DELETE(audio, us_audio_destroy);
sleep(1); // error_delay
}
return NULL;
}
#undef _IF_NOT_REPORTED
static void _relay_rtp_clients(const us_rtp_s *rtp) {
US_LIST_ITERATE(_g_clients, client, {
us_janus_client_send(client, rtp);
});
}
static int _plugin_init(janus_callbacks *gw, const char *config_dir_path) {
// https://groups.google.com/g/meetecho-janus/c/xoWIQfaoJm8
// sysctl -w net.core.rmem_default=500000
// sysctl -w net.core.wmem_default=500000
// sysctl -w net.core.rmem_max=1000000
// sysctl -w net.core.wmem_max=1000000
US_JLOG_INFO("main", "Initializing plugin ...");
if (gw == NULL || config_dir_path == NULL || ((_g_config = us_config_init(config_dir_path)) == NULL)) {
return -1;
}
_g_gw = gw;
_g_video_queue = us_queue_init(1024);
_g_rtpv = us_rtpv_init(_relay_rtp_clients, _g_config->video_zero_playout_delay);
if (_g_config->audio_dev_name != NULL) {
_g_rtpa = us_rtpa_init(_relay_rtp_clients);
US_THREAD_CREATE(_g_audio_tid, _audio_thread, NULL);
}
US_THREAD_CREATE(_g_video_rtp_tid, _video_rtp_thread, NULL);
US_THREAD_CREATE(_g_video_sink_tid, _video_sink_thread, NULL);
atomic_store(&_g_ready, true);
return 0;
}
static void _plugin_destroy(void) {
US_JLOG_INFO("main", "Destroying plugin ...");
atomic_store(&_g_stop, true);
# define JOIN(_tid) { if (atomic_load(&_tid##_created)) { US_THREAD_JOIN(_tid); } }
JOIN(_g_video_sink_tid);
JOIN(_g_video_rtp_tid);
JOIN(_g_audio_tid);
# undef JOIN
US_LIST_ITERATE(_g_clients, client, {
US_LIST_REMOVE(_g_clients, client);
us_janus_client_destroy(client);
});
US_QUEUE_DELETE_WITH_ITEMS(_g_video_queue, us_frame_destroy);
US_DELETE(_g_rtpa, us_rtpa_destroy);
US_DELETE(_g_rtpv, us_rtpv_destroy);
US_DELETE(_g_config, us_config_destroy);
}
#define _IF_DISABLED(...) { if (!_READY || _STOP) { __VA_ARGS__ } }
static void _plugin_create_session(janus_plugin_session *session, int *err) {
_IF_DISABLED({ *err = -1; return; });
_LOCK_ALL;
US_JLOG_INFO("main", "Creating session %p ...", session);
us_janus_client_s *const client = us_janus_client_init(_g_gw, session, (_g_config->audio_dev_name != NULL));
US_LIST_APPEND(_g_clients, client);
atomic_store(&_g_has_watchers, true);
_UNLOCK_ALL;
}
static void _plugin_destroy_session(janus_plugin_session* session, int *err) {
_IF_DISABLED({ *err = -1; return; });
_LOCK_ALL;
bool found = false;
bool has_watchers = false;
US_LIST_ITERATE(_g_clients, client, {
if (client->session == session) {
US_JLOG_INFO("main", "Removing session %p ...", session);
US_LIST_REMOVE(_g_clients, client);
us_janus_client_destroy(client);
found = true;
} else {
has_watchers = (has_watchers || atomic_load(&client->transmit));
}
});
if (!found) {
US_JLOG_WARN("main", "No session %p", session);
*err = -2;
}
atomic_store(&_g_has_watchers, has_watchers);
_UNLOCK_ALL;
}
static json_t *_plugin_query_session(janus_plugin_session *session) {
_IF_DISABLED({ return NULL; });
json_t *info = NULL;
_LOCK_ALL;
US_LIST_ITERATE(_g_clients, client, {
if (client->session == session) {
info = json_string("session_found");
break;
}
});
_UNLOCK_ALL;
return info;
}
static void _set_transmit(janus_plugin_session *session, UNUSED const char *msg, bool transmit) {
_IF_DISABLED({ return; });
_LOCK_ALL;
bool found = false;
bool has_watchers = false;
US_LIST_ITERATE(_g_clients, client, {
if (client->session == session) {
atomic_store(&client->transmit, transmit);
// US_JLOG_INFO("main", "%s session %p", msg, session);
found = true;
}
has_watchers = (has_watchers || atomic_load(&client->transmit));
});
if (!found) {
US_JLOG_WARN("main", "No session %p", session);
}
atomic_store(&_g_has_watchers, has_watchers);
_UNLOCK_ALL;
}
#undef _IF_DISABLED
static void _plugin_setup_media(janus_plugin_session *session) { _set_transmit(session, "Unmuted", true); }
static void _plugin_hangup_media(janus_plugin_session *session) { _set_transmit(session, "Muted", false); }
static struct janus_plugin_result *_plugin_handle_message(
janus_plugin_session *session, char *transaction, json_t *msg, json_t *jsep) {
assert(transaction != NULL);
# define FREE_MSG_JSEP { \
US_DELETE(msg, json_decref); \
US_DELETE(jsep, json_decref); \
}
if (session == NULL || msg == NULL) {
free(transaction);
FREE_MSG_JSEP;
return janus_plugin_result_new(JANUS_PLUGIN_ERROR, (msg ? "No session" : "No message"), NULL);
}
# define PUSH_ERROR(x_error, x_reason) { \
/*US_JLOG_ERROR("main", "Message error in session %p: %s", session, x_reason);*/ \
json_t *m_event = json_object(); \
json_object_set_new(m_event, "ustreamer", json_string("event")); \
json_object_set_new(m_event, "error_code", json_integer(x_error)); \
json_object_set_new(m_event, "error", json_string(x_reason)); \
_g_gw->push_event(session, create(), transaction, m_event, NULL); \
json_decref(m_event); \
}
json_t *const request_obj = json_object_get(msg, "request");
if (request_obj == NULL) {
PUSH_ERROR(400, "Request missing");
goto ok_wait;
}
const char *const request_str = json_string_value(request_obj);
if (request_str == NULL) {
PUSH_ERROR(400, "Request not a string");
goto ok_wait;
}
// US_JLOG_INFO("main", "Message: %s", request_str);
# define PUSH_STATUS(x_status, x_jsep) { \
json_t *const m_event = json_object(); \
json_object_set_new(m_event, "ustreamer", json_string("event")); \
json_t *const m_result = json_object(); \
json_object_set_new(m_result, "status", json_string(x_status)); \
json_object_set_new(m_event, "result", m_result); \
_g_gw->push_event(session, create(), transaction, m_event, x_jsep); \
json_decref(m_event); \
}
if (!strcmp(request_str, "start")) {
PUSH_STATUS("started", NULL);
} else if (!strcmp(request_str, "stop")) {
PUSH_STATUS("stopped", NULL);
} else if (!strcmp(request_str, "watch")) {
char *sdp;
{
char *const video_sdp = us_rtpv_make_sdp(_g_rtpv);
if (video_sdp == NULL) {
PUSH_ERROR(503, "Haven't received SPS/PPS from memsink yet");
goto ok_wait;
}
char *const audio_sdp = (_g_rtpa ? us_rtpa_make_sdp(_g_rtpa) : us_strdup(""));
US_ASPRINTF(sdp,
"v=0" RN
"o=- %" PRIu64 " 1 IN IP4 0.0.0.0" RN
"s=PiKVM uStreamer" RN
"t=0 0" RN
"%s%s",
us_get_now_id() >> 1, audio_sdp, video_sdp
);
free(audio_sdp);
free(video_sdp);
}
json_t *const offer_jsep = json_pack("{ssss}", "type", "offer", "sdp", sdp);
free(sdp);
PUSH_STATUS("started", offer_jsep);
json_decref(offer_jsep);
} else {
PUSH_ERROR(405, "Not implemented");
}
ok_wait:
FREE_MSG_JSEP;
return janus_plugin_result_new(JANUS_PLUGIN_OK_WAIT, NULL, NULL);
# undef PUSH_STATUS
# undef PUSH_ERROR
# undef FREE_MSG_JSEP
}
// ***** Plugin *****
static int _plugin_get_api_compatibility(void) { return JANUS_PLUGIN_API_VERSION; }
static int _plugin_get_version(void) { return US_VERSION_U; }
static const char *_plugin_get_version_string(void) { return US_VERSION; }
static const char *_plugin_get_description(void) { return "PiKVM uStreamer Janus plugin for H.264 video"; }
static const char *_plugin_get_name(void) { return US_PLUGIN_NAME; }
static const char *_plugin_get_author(void) { return "Maxim Devaev <mdevaev@gmail.com>"; }
static const char *_plugin_get_package(void) { return US_PLUGIN_PACKAGE; }
static void _plugin_incoming_rtp(UNUSED janus_plugin_session *handle, UNUSED janus_plugin_rtp *packet) {
// Just a stub to avoid logging spam about the plugin's purpose
}
janus_plugin *create(void) {
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Woverride-init"
static janus_plugin plugin = JANUS_PLUGIN_INIT(
.init = _plugin_init,
.destroy = _plugin_destroy,
.create_session = _plugin_create_session,
.destroy_session = _plugin_destroy_session,
.query_session = _plugin_query_session,
.setup_media = _plugin_setup_media,
.hangup_media = _plugin_hangup_media,
.handle_message = _plugin_handle_message,
.get_api_compatibility = _plugin_get_api_compatibility,
.get_version = _plugin_get_version,
.get_version_string = _plugin_get_version_string,
.get_description = _plugin_get_description,
.get_name = _plugin_get_name,
.get_author = _plugin_get_author,
.get_package = _plugin_get_package,
.incoming_rtp = _plugin_incoming_rtp,
);
# pragma GCC diagnostic pop
return &plugin;
}

102
janus/src/queue.c Normal file
View File

@@ -0,0 +1,102 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "queue.h"
us_queue_s *us_queue_init(unsigned capacity) {
us_queue_s *queue;
US_CALLOC(queue, 1);
US_CALLOC(queue->items, capacity);
queue->capacity = capacity;
US_MUTEX_INIT(queue->mutex);
pthread_condattr_t attrs;
assert(!pthread_condattr_init(&attrs));
assert(!pthread_condattr_setclock(&attrs, CLOCK_MONOTONIC));
assert(!pthread_cond_init(&queue->full_cond, &attrs));
assert(!pthread_cond_init(&queue->empty_cond, &attrs));
assert(!pthread_condattr_destroy(&attrs));
return queue;
}
void us_queue_destroy(us_queue_s *queue) {
US_COND_DESTROY(queue->empty_cond);
US_COND_DESTROY(queue->full_cond);
US_MUTEX_DESTROY(queue->mutex);
free(queue->items);
free(queue);
}
#define _WAIT_OR_UNLOCK(x_var, x_cond) { \
struct timespec m_ts; \
assert(!clock_gettime(CLOCK_MONOTONIC, &m_ts)); \
us_ld_to_timespec(us_timespec_to_ld(&m_ts) + timeout, &m_ts); \
while (x_var) { \
const int err = pthread_cond_timedwait(&(x_cond), &queue->mutex, &m_ts); \
if (err == ETIMEDOUT) { \
US_MUTEX_UNLOCK(queue->mutex); \
return -1; \
} \
assert(!err); \
} \
}
int us_queue_put(us_queue_s *queue, void *item, long double timeout) {
US_MUTEX_LOCK(queue->mutex);
if (timeout == 0) {
if (queue->size == queue->capacity) {
US_MUTEX_UNLOCK(queue->mutex);
return -1;
}
} else {
_WAIT_OR_UNLOCK(queue->size == queue->capacity, queue->full_cond);
}
queue->items[queue->in] = item;
++queue->size;
++queue->in;
queue->in %= queue->capacity;
US_MUTEX_UNLOCK(queue->mutex);
US_COND_BROADCAST(queue->empty_cond);
return 0;
}
int us_queue_get(us_queue_s *queue, void **item, long double timeout) {
US_MUTEX_LOCK(queue->mutex);
_WAIT_OR_UNLOCK(queue->size == 0, queue->empty_cond);
*item = queue->items[queue->out];
--queue->size;
++queue->out;
queue->out %= queue->capacity;
US_MUTEX_UNLOCK(queue->mutex);
US_COND_BROADCAST(queue->full_cond);
return 0;
}
#undef _WAIT_OR_UNLOCK
int us_queue_get_free(us_queue_s *queue) {
US_MUTEX_LOCK(queue->mutex);
const unsigned size = queue->size;
US_MUTEX_UNLOCK(queue->mutex);
return queue->capacity - size;
}

68
janus/src/queue.h Normal file
View File

@@ -0,0 +1,68 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <errno.h>
#include <time.h>
#include <assert.h>
#include <pthread.h>
#include "uslibs/tools.h"
#include "uslibs/threading.h"
// Based on https://github.com/seifzadeh/c-pthread-queue/blob/master/queue.h
typedef struct {
void **items;
unsigned size;
unsigned capacity;
unsigned in;
unsigned out;
pthread_mutex_t mutex;
pthread_cond_t full_cond;
pthread_cond_t empty_cond;
} us_queue_s;
#define US_QUEUE_DELETE_WITH_ITEMS(x_queue, x_free_item) { \
if (x_queue) { \
while (!us_queue_get_free(x_queue)) { \
void *m_ptr; \
if (!us_queue_get(x_queue, &m_ptr, 0)) { \
US_DELETE(m_ptr, x_free_item); \
} \
} \
us_queue_destroy(x_queue); \
} \
}
us_queue_s *us_queue_init(unsigned capacity);
void us_queue_destroy(us_queue_s *queue);
int us_queue_put(us_queue_s *queue, void *item, long double timeout);
int us_queue_get(us_queue_s *queue, void **item, long double timeout);
int us_queue_get_free(us_queue_s *queue);

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

@@ -0,0 +1,64 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# This source file is partially based on this code: #
# - https://github.com/catid/kvm/blob/master/kvm_pipeline/src #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "rtp.h"
us_rtp_s *us_rtp_init(unsigned payload, bool video, bool zero_playout_delay) {
us_rtp_s *rtp;
US_CALLOC(rtp, 1);
rtp->payload = payload;
rtp->video = video;
rtp->zero_playout_delay = zero_playout_delay; // See client.c
rtp->ssrc = us_triple_u32(us_get_now_monotonic_u64());
return rtp;
}
us_rtp_s *us_rtp_dup(const us_rtp_s *rtp) {
us_rtp_s *new;
US_CALLOC(new, 1);
memcpy(new, rtp, sizeof(us_rtp_s));
return new;
}
void us_rtp_destroy(us_rtp_s *rtp) {
free(rtp);
}
void us_rtp_write_header(us_rtp_s *rtp, uint32_t pts, bool marked) {
uint32_t word0 = 0x80000000;
if (marked) {
word0 |= 1 << 23;
}
word0 |= (rtp->payload & 0x7F) << 16;
word0 |= rtp->seq;
++rtp->seq;
# define WRITE_BE_U32(_offset, _value) *((uint32_t *)(rtp->datagram + _offset)) = __builtin_bswap32(_value)
WRITE_BE_U32(0, word0);
WRITE_BE_U32(4, pts);
WRITE_BE_U32(8, rtp->ssrc);
# undef WRITE_BE_U32
}

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

@@ -0,0 +1,57 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <sys/types.h>
#include "uslibs/tools.h"
// https://stackoverflow.com/questions/47635545/why-webrtc-chose-rtp-max-packet-size-to-1200-bytes
#define US_RTP_DATAGRAM_SIZE 1200
#define US_RTP_HEADER_SIZE 12
typedef struct {
unsigned payload;
bool video;
bool zero_playout_delay;
uint32_t ssrc;
uint16_t seq;
uint8_t datagram[US_RTP_DATAGRAM_SIZE];
size_t used;
} us_rtp_s;
typedef void (*us_rtp_callback_f)(const us_rtp_s *rtp);
us_rtp_s *us_rtp_init(unsigned payload, bool video, bool zero_playout_delay);
us_rtp_s *us_rtp_dup(const us_rtp_s *rtp);
void us_rtp_destroy(us_rtp_s *rtp);
void us_rtp_write_header(us_rtp_s *rtp, uint32_t pts, bool marked);

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

@@ -0,0 +1,66 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "rtpa.h"
us_rtpa_s *us_rtpa_init(us_rtp_callback_f callback) {
us_rtpa_s *rtpa;
US_CALLOC(rtpa, 1);
rtpa->rtp = us_rtp_init(111, false, false);
rtpa->callback = callback;
return rtpa;
}
void us_rtpa_destroy(us_rtpa_s *rtpa) {
us_rtp_destroy(rtpa->rtp);
free(rtpa);
}
char *us_rtpa_make_sdp(us_rtpa_s *rtpa) {
# define PAYLOAD rtpa->rtp->payload
char *sdp;
US_ASPRINTF(sdp,
"m=audio 1 RTP/SAVPF %u" RN
"c=IN IP4 0.0.0.0" RN
"a=rtpmap:%u OPUS/48000/2" RN
// "a=fmtp:%u useinbandfec=1" RN
"a=rtcp-fb:%u nack" RN
"a=rtcp-fb:%u nack pli" RN
"a=rtcp-fb:%u goog-remb" RN
"a=ssrc:%" PRIu32 " cname:ustreamer" RN
"a=sendonly" RN,
PAYLOAD, PAYLOAD, PAYLOAD, PAYLOAD, PAYLOAD, // PAYLOAD,
rtpa->rtp->ssrc
);
# undef PAYLOAD
return sdp;
}
void us_rtpa_wrap(us_rtpa_s *rtpa, const uint8_t *data, size_t size, uint32_t pts) {
if (size + US_RTP_HEADER_SIZE <= US_RTP_DATAGRAM_SIZE) {
us_rtp_write_header(rtpa->rtp, pts, false);
memcpy(rtpa->rtp->datagram + US_RTP_HEADER_SIZE, data, size);
rtpa->rtp->used = size + US_RTP_HEADER_SIZE;
rtpa->callback(rtpa->rtp);
}
}

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

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

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

@@ -0,0 +1,213 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# This source file is partially based on this code: #
# - https://github.com/catid/kvm/blob/master/kvm_pipeline/src #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "rtpv.h"
void _rtpv_process_nalu(us_rtpv_s *rtpv, const uint8_t *data, size_t size, uint32_t pts, bool marked);
static ssize_t _find_annexb(const uint8_t *data, size_t size);
us_rtpv_s *us_rtpv_init(us_rtp_callback_f callback, bool zero_playout_delay) {
us_rtpv_s *rtpv;
US_CALLOC(rtpv, 1);
rtpv->rtp = us_rtp_init(96, true, zero_playout_delay);
rtpv->callback = callback;
rtpv->sps = us_frame_init();
rtpv->pps = us_frame_init();
US_MUTEX_INIT(rtpv->mutex);
return rtpv;
}
void us_rtpv_destroy(us_rtpv_s *rtpv) {
US_MUTEX_DESTROY(rtpv->mutex);
us_frame_destroy(rtpv->pps);
us_frame_destroy(rtpv->sps);
us_rtp_destroy(rtpv->rtp);
free(rtpv);
}
char *us_rtpv_make_sdp(us_rtpv_s *rtpv) {
US_MUTEX_LOCK(rtpv->mutex);
if (rtpv->sps->used == 0 || rtpv->pps->used == 0) {
US_MUTEX_UNLOCK(rtpv->mutex);
return NULL;
}
char *sps = NULL;
char *pps = NULL;
us_base64_encode(rtpv->sps->data, rtpv->sps->used, &sps, NULL);
us_base64_encode(rtpv->pps->data, rtpv->pps->used, &pps, NULL);
US_MUTEX_UNLOCK(rtpv->mutex);
# define PAYLOAD rtpv->rtp->payload
// https://tools.ietf.org/html/rfc6184
// https://github.com/meetecho/janus-gateway/issues/2443
char *sdp;
US_ASPRINTF(sdp,
"m=video 1 RTP/SAVPF %u" RN
"c=IN IP4 0.0.0.0" RN
"a=rtpmap:%u H264/90000" RN
"a=fmtp:%u profile-level-id=42E01F" RN
"a=fmtp:%u packetization-mode=1" RN
"a=fmtp:%u sprop-sps=%s" RN
"a=fmtp:%u sprop-pps=%s" RN
"a=rtcp-fb:%u nack" RN
"a=rtcp-fb:%u nack pli" RN
"a=rtcp-fb:%u goog-remb" RN
"a=ssrc:%" PRIu32 " cname:ustreamer" RN
"%s" // playout-delay
"a=sendonly" RN,
PAYLOAD, PAYLOAD, PAYLOAD, PAYLOAD,
PAYLOAD, sps,
PAYLOAD, pps,
PAYLOAD, PAYLOAD, PAYLOAD,
rtpv->rtp->ssrc,
(rtpv->rtp->zero_playout_delay ? "a=extmap:1 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay" RN : "")
);
# undef PAYLOAD
free(sps);
free(pps);
return sdp;
}
#define _PRE 3 // Annex B prefix length
void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame) {
// There is a complicated logic here but everything works as it should:
// - https://github.com/pikvm/ustreamer/issues/115#issuecomment-893071775
assert(frame->format == V4L2_PIX_FMT_H264);
const uint32_t pts = us_get_now_monotonic_u64() * 9 / 100; // PTS units are in 90 kHz
ssize_t last_offset = -_PRE;
while (true) { // Find and iterate by nalus
const size_t next_start = last_offset + _PRE;
ssize_t offset = _find_annexb(frame->data + next_start, frame->used - next_start);
if (offset < 0) {
break;
}
offset += next_start;
if (last_offset >= 0) {
const uint8_t *const data = frame->data + last_offset + _PRE;
size_t size = offset - last_offset - _PRE;
if (data[size - 1] == 0) { // Check for extra 00
--size;
}
_rtpv_process_nalu(rtpv, data, size, pts, false);
}
last_offset = offset;
}
if (last_offset >= 0) {
const uint8_t *const data = frame->data + last_offset + _PRE;
size_t size = frame->used - last_offset - _PRE;
_rtpv_process_nalu(rtpv, data, size, pts, true);
}
}
void _rtpv_process_nalu(us_rtpv_s *rtpv, const uint8_t *data, size_t size, uint32_t pts, bool marked) {
const unsigned ref_idc = (data[0] >> 5) & 3;
const unsigned type = data[0] & 0x1F;
us_frame_s *ps = NULL;
switch (type) {
case 7: ps = rtpv->sps; break;
case 8: ps = rtpv->pps; break;
}
if (ps != NULL) {
US_MUTEX_LOCK(rtpv->mutex);
us_frame_set_data(ps, data, size);
US_MUTEX_UNLOCK(rtpv->mutex);
}
# define DG rtpv->rtp->datagram
if (size + US_RTP_HEADER_SIZE <= US_RTP_DATAGRAM_SIZE) {
us_rtp_write_header(rtpv->rtp, pts, marked);
memcpy(DG + US_RTP_HEADER_SIZE, data, size);
rtpv->rtp->used = size + US_RTP_HEADER_SIZE;
rtpv->callback(rtpv->rtp);
return;
}
const size_t fu_overhead = US_RTP_HEADER_SIZE + 2; // FU-A overhead
const uint8_t *src = data + 1;
ssize_t remaining = size - 1;
bool first = true;
while (remaining > 0) {
ssize_t frag_size = US_RTP_DATAGRAM_SIZE - fu_overhead;
const bool last = (remaining <= frag_size);
if (last) {
frag_size = remaining;
}
us_rtp_write_header(rtpv->rtp, pts, (marked && last));
DG[US_RTP_HEADER_SIZE] = 28 | (ref_idc << 5);
uint8_t fu = type;
if (first) {
fu |= 0x80;
}
if (last) {
fu |= 0x40;
}
DG[US_RTP_HEADER_SIZE + 1] = fu;
memcpy(DG + fu_overhead, src, frag_size);
rtpv->rtp->used = fu_overhead + frag_size;
rtpv->callback(rtpv->rtp);
src += frag_size;
remaining -= frag_size;
first = false;
}
# undef DG
}
static ssize_t _find_annexb(const uint8_t *data, size_t size) {
// Parses buffer for 00 00 01 start codes
if (size >= _PRE) {
for (size_t index = 0; index <= size - _PRE; ++index) {
if (data[index] == 0 && data[index + 1] == 0 && data[index + 2] == 1) {
return index;
}
}
}
return -1;
}
#undef _PRE

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

@@ -0,0 +1,57 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <inttypes.h>
#include <assert.h>
#include <sys/types.h>
#include <linux/videodev2.h>
#include <pthread.h>
#include "uslibs/tools.h"
#include "uslibs/threading.h"
#include "uslibs/frame.h"
#include "uslibs/base64.h"
#include "rtp.h"
typedef struct {
us_rtp_s *rtp;
us_rtp_callback_f callback;
us_frame_s *sps; // Actually not a frame, just a bytes storage
us_frame_s *pps;
pthread_mutex_t mutex;
} us_rtpv_s;
us_rtpv_s *us_rtpv_init(us_rtp_callback_f callback, bool zero_playout_delay);
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);

64
janus/src/tc358743.c Normal file
View File

@@ -0,0 +1,64 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "tc358743.h"
#ifndef V4L2_CID_USER_TC358743_BASE
# define V4L2_CID_USER_TC358743_BASE (V4L2_CID_USER_BASE + 0x1080)
#endif
#ifndef TC358743_CID_AUDIO_PRESENT
# define TC358743_CID_AUDIO_PRESENT (V4L2_CID_USER_TC358743_BASE + 1)
#endif
#ifndef TC358743_CID_AUDIO_SAMPLING_RATE
# define TC358743_CID_AUDIO_SAMPLING_RATE (V4L2_CID_USER_TC358743_BASE + 0)
#endif
int us_tc358743_read_info(const char *path, us_tc358743_info_s *info) {
US_MEMSET_ZERO(*info);
int fd = -1;
if ((fd = open(path, O_RDWR)) < 0) {
US_JLOG_PERROR("audio", "Can't open TC358743 V4L2 device");
return -1;
}
# define READ_CID(x_cid, x_field) { \
struct v4l2_control m_ctl = {0}; \
m_ctl.id = x_cid; \
if (us_xioctl(fd, VIDIOC_G_CTRL, &m_ctl) < 0) { \
US_JLOG_PERROR("audio", "Can't get value of " #x_cid); \
close(fd); \
return -1; \
} \
info->x_field = m_ctl.value; \
}
READ_CID(TC358743_CID_AUDIO_PRESENT, has_audio);
READ_CID(TC358743_CID_AUDIO_SAMPLING_RATE, audio_hz);
# undef READ_CID
close(fd);
return 0;
}

46
janus/src/tc358743.h Normal file
View File

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

View File

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

View File

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

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

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

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

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

View File

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

6
linters/cppcheck.h Normal file
View File

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

View File

@@ -15,21 +15,15 @@ disable =
locally-disabled,
fixme,
missing-docstring,
no-init,
no-self-use,
superfluous-parens,
abstract-class-not-used,
abstract-class-little-used,
duplicate-code,
bad-continuation,
bad-whitespace,
star-args,
broad-except,
redundant-keyword-arg,
wrong-import-order,
too-many-ancestors,
no-else-return,
len-as-condition,
unspecified-encoding,
[REPORTS]
msg-template = {symbol} -- {path}:{line}({obj}): {msg}
@@ -38,9 +32,6 @@ msg-template = {symbol} -- {path}:{line}({obj}): {msg}
max-line-length = 160
[BASIC]
# List of builtins function names that should not be used, separated by a comma
bad-functions =
# Good variable names which should always be accepted, separated by a comma
good-names = _, __, x, y, ws, make-html-h, make-jpeg-h

View File

@@ -3,47 +3,47 @@ envlist = cppcheck, flake8, pylint, mypy, vulture, htmlhint
skipsdist = true
[testenv]
basepython = python3.9
basepython = python3.10
changedir = /src
[testenv:cppcheck]
whitelist_externals = cppcheck
commands = cppcheck \
-j4 \
--force \
--std=c11 \
--std=c17 \
--error-exitcode=1 \
--quiet \
--enable=warning,unusedFunction,portability,performance,style \
--suppress=assignmentInAssert \
--suppress=variableScope \
--inline-suppr \
-DCHAR_BIT=8 \
-DWITH_OMX \
-DWITH_GPIO \
src
--library=python \
--include=linters/cppcheck.h \
src python/*.? janus/*.?
[testenv:flake8]
whitelist_externals = bash
commands = bash -c 'flake8 --config=linters/flake8.ini tools/*.py'
commands = bash -c 'flake8 --config=linters/flake8.ini tools/*.py' python/*.py
deps =
flake8
flake8-quotes
[testenv:pylint]
whitelist_externals = bash
commands = bash -c 'pylint --rcfile=linters/pylint.ini --output-format=colorized --reports=no tools/*.py'
commands = bash -c 'pylint --rcfile=linters/pylint.ini --output-format=colorized --reports=no tools/*.py python/*.py'
deps =
pylint
[testenv:mypy]
whitelist_externals = bash
commands = bash -c 'mypy --config-file=linters/mypy.ini tools/*.py'
commands = bash -c 'mypy --config-file=linters/mypy.ini tools/*.py python/*.py'
deps =
mypy
[testenv:vulture]
whitelist_externals = bash
commands = bash -c 'vulture tools/*.py'
commands = bash -c 'vulture tools/*.py python/*.py'
deps =
vulture

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 3.3" "January 2021"
.TH USTREAMER-DUMP 1 "version 5.21" "January 2021"
.SH NAME
ustreamer-dump \- Dump uStreamer's memory sink to file
@@ -38,6 +38,12 @@ Filename to dump output to. Use '-' for stdout. Default: just consume the sink.
.TP
.BR \-j ", " \-\-output-json
Format output as JSON. Required option --output. Default: disabled.
.TP
.BR \-c ", " \-\-count\ \fIN
Limit the number of frames. Default: 0 (infinite).
.TP
.BR \-i ", "\-\-interval\ \fIsec
Delay between reading frames (float). Default: 0.
.SS "Logging options"
.TP

View File

@@ -1,16 +1,16 @@
.\" Manpage for ustreamer.
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
.TH USTREAMER 1 "version 3.3" "November 2020"
.TH USTREAMER 1 "version 5.21" "November 2020"
.SH NAME
ustreamer \- stream MJPG video from any V4L2 device to the network
ustreamer \- stream MJPEG video from any V4L2 device to the network
.SH SYNOPSIS
.B ustreamer
.RI [OPTIONS]
.SH DESCRIPTION
µStreamer (\fBustreamer\fP) is a lightweight and very quick server to stream MJPG video from any V4L2 device to the network. All new browsers have native support of this video format, as well as most video players such as mplayer, VLC etc. µStreamer is a part of the Pi-KVM project designed to stream VGA and HDMI screencast hardware data with the highest resolution and FPS possible.
µStreamer (\fBustreamer\fP) is a lightweight and very quick server to stream MJPEG video from any V4L2 device to the network. All new browsers have native support of this video format, as well as most video players such as mplayer, VLC etc. µStreamer is a part of the PiKVM project designed to stream VGA and HDMI screencast hardware data with the highest resolution and FPS possible.
.SH USAGE
Without arguments, \fBustreamer\fR will try to open \fB/dev/video0\fR with 640x480 resolution and start streaming on \fBhttp://127\.0\.0\.1:8080\fR\. You can override this behavior using parameters \fB\-\-device\fR, \fB\-\-host\fR and \fB\-\-port\fR\. For example, to stream to the world, run: \fBustreamer --device=/dev/video1 --host=0.0.0.0 --port=80\fR
@@ -23,9 +23,9 @@ For example, the recommended way of running µStreamer with Auvidea B101 on a Ra
.RS
\fB\-\-format=uyvy \e\fR # Device input format
.nf
\fB\-\-encoder=omx \e\fR # Hardware encoding with OpenMAX
\fB\-\-encoder=m2m-image \e\fR # Hardware encoding with V4L2 M2M intraface
.nf
\fB\-\-workers=3 \e\fR # Maximum workers for OpenMAX
\fB\-\-workers=3 \e\fR # Maximum workers for V4L2 encoder
.nf
\fB\-\-persistent \e\fR # Don\'t re\-initialize device on timeout (for example when HDMI cable was disconnected)
.nf
@@ -72,7 +72,7 @@ Drop frames smaller then this limit. Useful if the device produces small\-sized
Don't re\-initialize device on timeout. Default: disabled.
.TP
.BR \-t ", " \-\-dv\-timings
Enable DV timings querying and events processing to automatic resolution change. Default: disabled.
Enable DV-timings querying and events processing to automatic resolution change. Default: disabled.
.TP
.BR \-b\ \fIN ", " \-\-buffers\ \fIN
The number of buffers to receive data from the device. Each buffer may processed using an independent thread.
@@ -84,27 +84,29 @@ Default: 1 (the number of CPU cores (but not more than 4)).
.TP
.BR \-q\ \fIN ", " \-\-quality\ \fIN
Set quality of JPEG encoding from 1 to 100 (best). Default: 80.
Note: If HW encoding is used (JPEG source format selected), this parameter attempts to configure the camera or capture device hardware's internal encoder. It does not re\-encode MJPG to MJPG to change the quality level for sources that already output MJPG.
Note: If HW encoding is used (JPEG source format selected), this parameter attempts to configure the camera or capture device hardware's internal encoder. It does not re\-encode MJPEG to MJPEG to change the quality level for sources that already output MJPEG.
.TP
.BR \-c\ \fItype ", " \-\-encoder\ \fItype
Use specified encoder. It may affect the number of workers.
CPU ─ Software MJPG encoding (default).
CPU ─ Software MJPEG encoding (default).
OMX ─ GPU hardware accelerated MJPG encoding with OpenMax (required \fBWITH_OMX\fR feature).
HW ─ Use pre-encoded MJPEG frames directly from camera hardware.
HW ─ Use pre-encoded MJPG frames directly from camera hardware.
M2M-VIDEO ─ GPU-accelerated MJPEG encoding.
NOOP ─ Don't compress MJPG stream (do nothing).
M2M-IMAGE ─ GPU-accelerated JPEG encoding.
NOOP ─ Don't compress MJPEG stream (do nothing).
.TP
.BR \-g\ \fIWxH,... ", " \-\-glitched\-resolutions\ \fIWxH,...
It doesn't do anything. Still here for compatibility. Required \fBWITH_OMX\fR feature.
It doesn't do anything. Still here for compatibility.
.TP
.BR \-k\ \fIpath ", " \-\-blank\ \fIpath
Path to JPEG file that will be shown when the device is disconnected during the streaming. Default: black screen 640x480 with 'NO SIGNAL'.
.TP
.BR \-K\ \fIsec ", " \-\-last\-as\-blank\ \fIsec
Show the last frame received from the camera after it was disconnected, but no more than specified time (or endlessly if 0 is specified). If the device has not yet been online, display 'NO SIGNAL' or the image specified by option \-\-blank. 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 'NO SIGNAL' or the image specified by option \-\-blank. Note: currently this option has no effect on memory sinks. Default: disabled.
.TP
.BR \-l ", " \-\-slowdown
Slowdown capturing to 1 FPS or less when no stream or sink clients are connected. Useful to reduce CPU consumption. Default: disabled.
@@ -114,11 +116,14 @@ Timeout for device querying. Default: 1.
.TP
.BR \-\-device\-error\-delay\ \fIsec
Delay before trying to connect to the device again after an error (timeout for example). Default: 1.
.TP
.BR \-\-m2m\-device\ \fI/dev/path
Path to V4L2 mem-to-mem encoder device. Default: auto-select.
.SS "Image control options"
.TP
.BR \-\-image\-default
Reset all image settings bellow to default. Default: no change.
Reset all image settings below to default. Default: no change.
.TP
.BR \-\-brightness\ \fIN ", " \fIauto ", " \fIdefault
Set brightness. Default: no change.
@@ -166,13 +171,16 @@ Bind to this TCP port. Default: 8080.
.TP
.BR \-U\ \fIpath ", " \-\-unix\ \fIpath
Bind to UNIX domain socket. Default: disabled.
.tp
.br \-d ", " \-\-unix\-rm
.TP
.BR \-D ", " \-\-unix\-rm
Try to remove old unix socket file before binding. default: disabled.
.TP
.BR \-M\ \fImode ", " \-\-unix\-mode\ \fImode
Set UNIX socket file permissions (like 777). Default: disabled.
.TP
.BR \-S ", " \-\-systemd
Bind to systemd socket for socket activation. Required \fBWITH_SYSTEMD\fR feature. Default: disabled.
.TP
.BR \-\-user\ \fIname
HTTP basic auth user. Default: disabled.
.TP
@@ -189,7 +197,7 @@ Don't send identical frames to clients, but no more than specified number. It ca
Override image resolution for the /state. Default: disabled.
.TP
.BR \-\-tcp\-nodelay
Set TCP_NODELAY flag to the client /stream socket. Ignored for \-\-unix.
Set TCP_NODELAY flag to the client /stream socket. Only for TCP socket.
Default: disabled.
.TP
.BR \-\-allow\-origin\ \fIstr
@@ -210,15 +218,16 @@ Set JPEG sink permissions (like 777). Default: 660.
.BR \-\-sink\-rm
Remove shared memory on stop. Default: disabled.
.TP
.BR \-\-sink\-client\-ttl\ \fIsec
Client TTL. Default: 10.
.TP
.BR \-\-sink\-timeout\ \fIsec
Timeout for lock. Default: 1.
.SS "H264 sink options"
.TP
Available only if \fBWITH_OMX\fR feature enabled.
.TP
.BR \-\-h264\-sink\ \fIname
Use the specified shared memory object to sink H264 frames encoded by MMAL. Default: disabled.
Use the specified shared memory object to sink H264 frames. Default: disabled.
.TP
.BR \-\-h264\-sink\-mode\ \fImode
Set H264 sink permissions (like 777). Default: 660.
@@ -226,14 +235,30 @@ Set H264 sink permissions (like 777). Default: 660.
.BR \-\-h264\-sink\-rm
Remove shared memory on stop. Default: disabled.
.TP
.BR \-\-h264\-sink\-client\-ttl\ \fIsec
Client TTL. Default: 10.
.TP
.BR \-\-h264\-sink\-timeout\ \fIsec
Timeout for lock. Default: 1.
.TP
.BR \-\-h264\-bitrate\ \fIkbps
H264 bitrate in Kbps. Default: 5000.
.TP
.BR \-\-h264\-gop\ \fIN
Intarval between keyframes. Default: 30.
.TP
.BR \-\-h264\-m2m\-device\ \fI/dev/path
Path to V4L2 mem-to-mem encoder device. Default: auto-select.
.SS "Process options"
.TP
.BR \-\-exit\-on\-parent\-death
Exit the program if the parent process is dead. Required \fBHAS_PDEATHSIG\fR feature. Default: disabled.
.TP
.BR \-\-exit\-on\-no\-clients \fIsec
Exit the program if there have been no stream or sink clients or any HTTP requests in the last N seconds. Default: 0 (disabled).
.TP
.BR \-\-process\-name\-prefix\ \fIstr
Set process name prefix which will be displayed in the process list like '\fIstr: ustreamer \-\-blah\-blah\-blah'\fR. Required \fBWITH_SETPROCTITLE\fR feature. Default: disabled.
.TP

View File

@@ -3,32 +3,46 @@
pkgname=ustreamer
pkgver=3.3
pkgver=5.21
pkgrel=1
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
pkgdesc="Lightweight and fast MJPEG-HTTP streamer"
url="https://github.com/pikvm/ustreamer"
license=(GPL)
arch=(i686 x86_64 armv6h armv7h aarch64)
depends=(libjpeg libevent libutil-linux libbsd libgpiod)
# optional: raspberrypi-firmware for OMX encoder
makedepends=(gcc make)
depends=(libjpeg libevent libbsd libgpiod systemd)
makedepends=(gcc make systemd)
source=(${pkgname}::"git+https://github.com/pikvm/ustreamer#commit=v${pkgver}")
md5sums=(SKIP)
_options="WITH_GPIO=1 WITH_SYSTEMD=1"
if [ -e /usr/bin/python3 ]; then
_options="$_options WITH_PYTHON=1"
depends+=(python)
makedepends+=(python-setuptools)
fi
if [ -e /usr/include/janus/plugins/plugin.h ];then
depends+=(janus-gateway alsa-lib opus)
makedepends+=(janus-gateway alsa-lib opus)
_options="$_options WITH_JANUS=1"
fi
# LD does not link mmal with this option
# This DOESN'T affect setup.py
LDFLAGS="${LDFLAGS//--as-needed/}"
export LDFLAGS="${LDFLAGS//,,/,}"
build() {
cd "$srcdir"
rm -rf $pkgname-build
cp -r $pkgname $pkgname-build
cd $pkgname-build
local _options="WITH_GPIO=1"
[ -e /opt/vc/include/IL/OMX_Core.h ] && _options="$_options WITH_OMX=1"
make $_options CFLAGS="$CFLAGS" LDFLAGS="$LDFLAGS" $MAKEFLAGS
}
package() {
cd "$srcdir/$pkgname-build"
make DESTDIR="$pkgdir" PREFIX=/usr install
make $_options CFLAGS="$CFLAGS" LDFLAGS="$LDFLAGS" DESTDIR="$pkgdir" PREFIX=/usr install
}

View File

@@ -7,13 +7,12 @@ RUN apt-get update \
gcc \
libjpeg8-dev \
libbsd-dev \
libraspberrypi-dev \
libgpiod-dev \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /build/ustreamer/
COPY . .
RUN make -j5 WITH_OMX=1 WITH_GPIO=1
RUN make -j5 WITH_GPIO=1
RUN ["cross-build-end"]
FROM balenalib/raspberrypi3-debian:run as RUN
@@ -25,7 +24,6 @@ RUN apt-get update \
libevent-2.1 \
libevent-pthreads-2.1-6 \
libjpeg8 \
uuid \
libbsd0 \
libgpiod2 \
&& rm -rf /var/lib/apt/lists/*

View File

@@ -5,13 +5,12 @@ RUN apt-get update \
gcc \
libjpeg8-dev \
libbsd-dev \
libraspberrypi-dev \
libgpiod-dev \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /build/ustreamer/
COPY . .
RUN make -j5 WITH_OMX=1 WITH_GPIO=1
RUN make -j5 WITH_GPIO=1
FROM balenalib/raspberrypi3-debian:run as RUN
@@ -20,7 +19,6 @@ RUN apt-get update \
libevent-2.1 \
libevent-pthreads-2.1-6 \
libjpeg8 \
uuid \
libbsd0 \
libgpiod2 \
&& rm -rf /var/lib/apt/lists/*

View File

@@ -8,7 +8,6 @@ RUN apt-get update \
git \
libevent-dev \
libjpeg62-turbo-dev \
uuid-dev \
libbsd-dev \
libgpiod-dev \
&& rm -rf /var/lib/apt/lists/*
@@ -25,7 +24,6 @@ RUN apt-get update \
libevent-2.1 \
libevent-pthreads-2.1-6 \
libjpeg62-turbo \
uuid \
libbsd0 \
libgpiod2 \
&& rm -rf /var/lib/apt/lists/*

View File

@@ -5,7 +5,7 @@ EAPI=7
inherit git-r3
DESCRIPTION="uStreamer - Lightweight and fast MJPG-HTTP streamer"
DESCRIPTION="uStreamer - Lightweight and fast MJPEG-HTTP streamer"
HOMEPAGE="https://github.com/pikvm/ustreamer"
EGIT_REPO_URI="https://github.com/pikvm/ustreamer.git"
@@ -17,7 +17,6 @@ IUSE=""
DEPEND="
>=dev-libs/libevent-2.1.8
>=media-libs/libjpeg-turbo-1.5.3
>=sys-apps/util-linux-2.33
>=dev-libs/libbsd-0.9.1
"
RDEPEND="${DEPEND}"

View File

@@ -6,7 +6,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=ustreamer
PKG_VERSION:=3.3
PKG_VERSION:=5.21
PKG_RELEASE:=1
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
@@ -25,12 +25,12 @@ define Package/ustreamer
SECTION:=multimedia
CATEGORY:=Multimedia
TITLE:=uStreamer
DEPENDS:=+libpthread +libjpeg +libv4l +libuuid +libbsd +libevent2 +libevent2-core +libevent2-extra +libevent2-pthreads
DEPENDS:=+libpthread +libjpeg +libv4l +libbsd +libevent2 +libevent2-core +libevent2-extra +libevent2-pthreads
URL:=https://github.com/pikvm/ustreamer
endef
define Package/ustreamer/description
µStreamer - Lightweight and fast MJPG-HTTP streamer
µStreamer - Lightweight and fast MJPEG-HTTP streamer
endef
define Package/ustreamer/install

20
python/Makefile Normal file
View File

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

37
python/setup.py Normal file
View File

@@ -0,0 +1,37 @@
import os
from typing import List
from setuptools import Extension
from setuptools import setup
# =====
def _find_sources(suffix: str) -> List[str]:
sources: List[str] = []
for (root_path, _, names) in os.walk("src"):
for name in names:
if name.endswith(suffix):
sources.append(os.path.join(root_path, name))
return sources
if __name__ == "__main__":
setup(
name="ustreamer",
version="5.21",
description="uStreamer tools",
author="Maxim Devaev",
author_email="mdevaev@gmail.com",
url="https://github.com/pikvm/ustreamer",
ext_modules=[
Extension(
"ustreamer",
libraries=["rt", "m", "pthread"],
extra_compile_args=["-std=c17", "-D_GNU_SOURCE"],
undef_macros=["NDEBUG"],
sources=_find_sources(".c"),
depends=_find_sources(".h"),
),
],
)

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

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

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

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

View File

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

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

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

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

@@ -0,0 +1,315 @@
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <Python.h>
#include "uslibs/tools.h"
#include "uslibs/frame.h"
#include "uslibs/memsinksh.h"
typedef struct {
PyObject_HEAD
char *obj;
double lock_timeout;
double wait_timeout;
double drop_same_frames;
int fd;
us_memsink_shared_s *mem;
uint64_t frame_id;
long double frame_ts;
us_frame_s *frame;
} _MemsinkObject;
#define _MEM(x_next) self->mem->x_next
#define _FRAME(x_next) self->frame->x_next
static void _MemsinkObject_destroy_internals(_MemsinkObject *self) {
if (self->mem != NULL) {
us_memsink_shared_unmap(self->mem);
self->mem = NULL;
}
if (self->fd >= 0) {
close(self->fd);
self->fd = -1;
}
if (self->frame != NULL) {
us_frame_destroy(self->frame);
self->frame = NULL;
}
}
static int _MemsinkObject_init(_MemsinkObject *self, PyObject *args, PyObject *kwargs) {
self->lock_timeout = 1;
self->wait_timeout = 1;
static char *kws[] = {"obj", "lock_timeout", "wait_timeout", "drop_same_frames", NULL};
if (!PyArg_ParseTupleAndKeywords(
args, kwargs, "s|ddd", kws,
&self->obj, &self->lock_timeout, &self->wait_timeout, &self->drop_same_frames)) {
return -1;
}
# define SET_DOUBLE(_field, _cond) { \
if (!(self->_field _cond)) { \
PyErr_SetString(PyExc_ValueError, #_field " must be " #_cond); \
return -1; \
} \
}
SET_DOUBLE(lock_timeout, > 0);
SET_DOUBLE(wait_timeout, > 0);
SET_DOUBLE(drop_same_frames, >= 0);
# undef SET_DOUBLE
self->frame = us_frame_init();
if ((self->fd = shm_open(self->obj, O_RDWR, 0)) == -1) {
PyErr_SetFromErrno(PyExc_OSError);
goto error;
}
if ((self->mem = us_memsink_shared_map(self->fd)) == NULL) {
PyErr_SetFromErrno(PyExc_OSError);
goto error;
}
return 0;
error:
_MemsinkObject_destroy_internals(self);
return -1;
}
static PyObject *_MemsinkObject_repr(_MemsinkObject *self) {
char repr[1024];
snprintf(repr, 1023, "<Memsink(%s)>", self->obj);
return Py_BuildValue("s", repr);
}
static void _MemsinkObject_dealloc(_MemsinkObject *self) {
_MemsinkObject_destroy_internals(self);
PyObject_Del(self);
}
static PyObject *_MemsinkObject_close(_MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
_MemsinkObject_destroy_internals(self);
Py_RETURN_NONE;
}
static PyObject *_MemsinkObject_enter(_MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
Py_INCREF(self);
return (PyObject *)self;
}
static PyObject *_MemsinkObject_exit(_MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
return PyObject_CallMethod((PyObject *)self, "close", "");
}
static int _wait_frame(_MemsinkObject *self) {
const long double deadline_ts = us_get_now_monotonic() + self->wait_timeout;
# define RETURN_OS_ERROR { \
Py_BLOCK_THREADS \
PyErr_SetFromErrno(PyExc_OSError); \
return -1; \
}
long double now;
do {
Py_BEGIN_ALLOW_THREADS
const int retval = us_flock_timedwait_monotonic(self->fd, self->lock_timeout);
now = us_get_now_monotonic();
if (retval < 0 && errno != EWOULDBLOCK) {
RETURN_OS_ERROR;
} else if (retval == 0) {
if (_MEM(magic) == US_MEMSINK_MAGIC && _MEM(version) == US_MEMSINK_VERSION && _MEM(id) != self->frame_id) {
if (self->drop_same_frames > 0) {
if (
US_FRAME_COMPARE_META_USED_NOTS(self->mem, self->frame)
&& (self->frame_ts + self->drop_same_frames > now)
&& !memcmp(_FRAME(data), _MEM(data), _MEM(used))
) {
self->frame_id = _MEM(id);
goto drop;
}
}
Py_BLOCK_THREADS
return 0;
}
if (flock(self->fd, LOCK_UN) < 0) {
RETURN_OS_ERROR;
}
}
drop:
if (usleep(1000) < 0) {
RETURN_OS_ERROR;
}
Py_END_ALLOW_THREADS
if (PyErr_CheckSignals() < 0) {
return -1;
}
} while (now < deadline_ts);
# undef RETURN_OS_ERROR
return -2;
}
static PyObject *_MemsinkObject_wait_frame(_MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
if (self->mem == NULL || self->fd <= 0) {
PyErr_SetString(PyExc_RuntimeError, "Closed");
return NULL;
}
switch (_wait_frame(self)) {
case 0: break;
case -2: Py_RETURN_NONE;
default: return NULL;
}
us_frame_set_data(self->frame, _MEM(data), _MEM(used));
US_FRAME_COPY_META(self->mem, self->frame);
self->frame_id = _MEM(id);
self->frame_ts = us_get_now_monotonic();
_MEM(last_client_ts) = self->frame_ts;
if (flock(self->fd, LOCK_UN) < 0) {
return PyErr_SetFromErrno(PyExc_OSError);
}
PyObject *dict_frame = PyDict_New();
if (dict_frame == NULL) {
return NULL;
}
# define SET_VALUE(_key, _maker) { \
PyObject *_tmp = _maker; \
if (_tmp == NULL) { \
return NULL; \
} \
if (PyDict_SetItemString(dict_frame, _key, _tmp) < 0) { \
Py_DECREF(_tmp); \
return NULL; \
} \
Py_DECREF(_tmp); \
}
# define SET_NUMBER(_key, _from, _to) SET_VALUE(#_key, Py##_to##_From##_from(_FRAME(_key)))
SET_NUMBER(width, Long, Long);
SET_NUMBER(height, Long, Long);
SET_NUMBER(format, Long, Long);
SET_NUMBER(stride, Long, Long);
SET_NUMBER(online, Long, Bool);
SET_NUMBER(key, Long, Bool);
SET_NUMBER(grab_ts, Double, Float);
SET_NUMBER(encode_begin_ts, Double, Float);
SET_NUMBER(encode_end_ts, Double, Float);
SET_VALUE("data", PyBytes_FromStringAndSize((const char *)_FRAME(data), _FRAME(used)));
# undef SET_NUMBER
# undef SET_VALUE
return dict_frame;
}
static PyObject *_MemsinkObject_is_opened(_MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
return PyBool_FromLong(self->mem != NULL && self->fd > 0);
}
#define FIELD_GETTER(_field, _from, _to) \
static PyObject *_MemsinkObject_getter_##_field(_MemsinkObject *self, void *Py_UNUSED(closure)) { \
return Py##_to##_From##_from(self->_field); \
}
FIELD_GETTER(obj, String, Unicode)
FIELD_GETTER(lock_timeout, Double, Float)
FIELD_GETTER(wait_timeout, Double, Float)
FIELD_GETTER(drop_same_frames, Double, Float)
#undef FIELD_GETTER
static PyMethodDef _MemsinkObject_methods[] = {
# define ADD_METHOD(_name, _method, _flags) \
{.ml_name = _name, .ml_meth = (PyCFunction)_MemsinkObject_##_method, .ml_flags = (_flags)}
ADD_METHOD("close", close, METH_NOARGS),
ADD_METHOD("__enter__", enter, METH_NOARGS),
ADD_METHOD("__exit__", exit, METH_VARARGS),
ADD_METHOD("wait_frame", wait_frame, METH_NOARGS),
ADD_METHOD("is_opened", is_opened, METH_NOARGS),
{},
# undef ADD_METHOD
};
static PyGetSetDef _MemsinkObject_getsets[] = {
# define ADD_GETTER(_field) {.name = #_field, .get = (getter)_MemsinkObject_getter_##_field}
ADD_GETTER(obj),
ADD_GETTER(lock_timeout),
ADD_GETTER(wait_timeout),
ADD_GETTER(drop_same_frames),
{},
# undef ADD_GETTER
};
static PyTypeObject _MemsinkType = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "ustreamer.Memsink",
.tp_basicsize = sizeof(_MemsinkObject),
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_new = PyType_GenericNew,
.tp_init = (initproc)_MemsinkObject_init,
.tp_dealloc = (destructor)_MemsinkObject_dealloc,
.tp_repr = (reprfunc)_MemsinkObject_repr,
.tp_methods = _MemsinkObject_methods,
.tp_getset = _MemsinkObject_getsets,
};
static PyModuleDef _Module = {
PyModuleDef_HEAD_INIT,
.m_name = "ustreamer",
.m_size = -1,
};
PyMODINIT_FUNC PyInit_ustreamer(void) { // cppcheck-suppress unusedFunction
PyObject *module = PyModule_Create(&_Module);
if (module == NULL) {
return NULL;
}
if (PyType_Ready(&_MemsinkType) < 0) {
return NULL;
}
Py_INCREF(&_MemsinkType);
if (PyModule_AddObject(module, "Memsink", (PyObject *)&_MemsinkType) < 0) {
return NULL;
}
return module;
}

108
src/Makefile Normal file
View File

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

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

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

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

@@ -0,0 +1,49 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <sys/types.h>
#include "../libs/tools.h"
#include "../libs/logging.h"
#include "../libs/frame.h"
#include "../libs/base64.h"
typedef struct {
const char *path;
bool json;
FILE *fp;
char *base64_data;
size_t base64_allocated;
} us_output_file_s;
us_output_file_s *us_output_file_init(const char *path, bool json);
void us_output_file_write(void *v_output, const us_frame_s *frame);
void us_output_file_destroy(void *v_output);

View File

@@ -1,8 +1,8 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -23,17 +23,22 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <signal.h>
#include <limits.h>
#include <float.h>
#include <getopt.h>
#include <errno.h>
#include <assert.h>
#include "../libs/config.h"
#include "../libs/const.h"
#include "../libs/tools.h"
#include "../libs/logging.h"
#include "../libs/frame.h"
#include "../libs/memsink.h"
#include "../libs/base64.h"
#include "../libs/options.h"
#include "file.h"
enum _OPT_VALUES {
@@ -41,6 +46,8 @@ enum _OPT_VALUES {
_O_SINK_TIMEOUT = 't',
_O_OUTPUT = 'o',
_O_OUTPUT_JSON = 'j',
_O_COUNT = 'c',
_O_INTERVAL = 'i',
_O_HELP = 'h',
_O_VERSION = 'v',
@@ -58,6 +65,8 @@ static const struct option _LONG_OPTS[] = {
{"sink-timeout", required_argument, NULL, _O_SINK_TIMEOUT},
{"output", required_argument, NULL, _O_OUTPUT},
{"output-json", no_argument, NULL, _O_OUTPUT_JSON},
{"count", required_argument, NULL, _O_COUNT},
{"interval", required_argument, NULL, _O_INTERVAL},
{"log-level", required_argument, NULL, _O_LOG_LEVEL},
{"perf", no_argument, NULL, _O_PERF},
@@ -73,25 +82,37 @@ static const struct option _LONG_OPTS[] = {
};
volatile bool stop = false;
volatile bool _g_stop = false;
typedef struct {
void *v_output;
void (*write)(void *v_output, const us_frame_s *frame);
void (*destroy)(void *v_output);
} _output_context_s;
static void _signal_handler(int signum);
static void _install_signal_handlers(void);
static int _dump_sink(const char *sink_name, unsigned sink_timeout, const char *output_path, bool output_json);
static int _dump_sink(
const char *sink_name, unsigned sink_timeout,
long long count, long double interval,
_output_context_s *ctx);
static void _help(FILE *fp);
int main(int argc, char *argv[]) {
LOGGING_INIT;
A_THREAD_RENAME("main");
US_LOGGING_INIT;
US_THREAD_RENAME("main");
char *sink_name = NULL;
unsigned sink_timeout = 1;
char *output_path = NULL;
bool output_json = false;
long long count = 0;
long double interval = 0;
# define OPT_SET(_dest, _value) { \
_dest = _value; \
@@ -108,28 +129,44 @@ int main(int argc, char *argv[]) {
break; \
}
for (int ch; (ch = getopt_long(argc, argv, "s:t:o:jhv", _LONG_OPTS, NULL)) >= 0;) {
# define OPT_LDOUBLE(_name, _dest, _min, _max) { \
errno = 0; char *_end = NULL; long double _tmp = strtold(optarg, &_end); \
if (errno || *_end || _tmp < _min || _tmp > _max) { \
printf("Invalid value for '%s=%s': min=%Lf, max=%Lf\n", _name, optarg, (long double)_min, (long double)_max); \
return 1; \
} \
_dest = _tmp; \
break; \
}
char short_opts[128];
us_build_short_options(_LONG_OPTS, short_opts, 128);
for (int ch; (ch = getopt_long(argc, argv, short_opts, _LONG_OPTS, NULL)) >= 0;) {
switch (ch) {
case _O_SINK: OPT_SET(sink_name, optarg);
case _O_SINK_TIMEOUT: OPT_NUMBER("--sink-timeout", sink_timeout, 1, 60, 0);
case _O_OUTPUT: OPT_SET(output_path, optarg);
case _O_OUTPUT_JSON: OPT_SET(output_json, true);
case _O_COUNT: OPT_NUMBER("--count", count, 0, LLONG_MAX, 0);
case _O_INTERVAL: OPT_LDOUBLE("--interval", interval, 0, 60);
case _O_LOG_LEVEL: OPT_NUMBER("--log-level", log_level, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, 0);
case _O_PERF: OPT_SET(log_level, LOG_LEVEL_PERF);
case _O_VERBOSE: OPT_SET(log_level, LOG_LEVEL_VERBOSE);
case _O_DEBUG: OPT_SET(log_level, LOG_LEVEL_DEBUG);
case _O_FORCE_LOG_COLORS: OPT_SET(log_colored, true);
case _O_NO_LOG_COLORS: OPT_SET(log_colored, false);
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(VERSION); return 0;
case _O_VERSION: puts(US_VERSION); return 0;
case 0: break;
default: _help(stderr); return 1;
default: return 1;
}
}
# undef OPT_LDOUBLE
# undef OPT_NUMBER
# undef OPT_SET
@@ -138,24 +175,34 @@ int main(int argc, char *argv[]) {
return 1;
}
_output_context_s ctx = {0};
if (output_path && output_path[0] != '\0') {
if ((ctx.v_output = (void *)us_output_file_init(output_path, output_json)) == NULL) {
return 1;
}
ctx.write = us_output_file_write;
ctx.destroy = us_output_file_destroy;
}
_install_signal_handlers();
return abs(_dump_sink(sink_name, sink_timeout, output_path, output_json));
const int retval = abs(_dump_sink(sink_name, sink_timeout, count, interval, &ctx));
if (ctx.v_output && ctx.destroy) {
ctx.destroy(ctx.v_output);
}
return retval;
}
static void _signal_handler(int signum) {
switch (signum) {
case SIGTERM: LOG_INFO_NOLOCK("===== Stopping by SIGTERM ====="); break;
case SIGINT: LOG_INFO_NOLOCK("===== Stopping by SIGINT ====="); break;
case SIGPIPE: LOG_INFO_NOLOCK("===== Stopping by SIGPIPE ====="); break;
default: LOG_INFO_NOLOCK("===== Stopping by %d =====", signum); break;
}
stop = true;
char *const name = us_signum_to_string(signum);
US_LOG_INFO_NOLOCK("===== Stopping by %s =====", name);
free(name);
_g_stop = true;
}
static void _install_signal_handlers(void) {
struct sigaction sig_act;
MEMSET_ZERO(sig_act);
struct sigaction sig_act = {0};
assert(!sigemptyset(&sig_act.sa_mask));
sig_act.sa_handler = _signal_handler;
@@ -163,37 +210,31 @@ static void _install_signal_handlers(void) {
assert(!sigaddset(&sig_act.sa_mask, SIGTERM));
assert(!sigaddset(&sig_act.sa_mask, SIGPIPE));
LOG_DEBUG("Installing SIGINT handler ...");
US_LOG_DEBUG("Installing SIGINT handler ...");
assert(!sigaction(SIGINT, &sig_act, NULL));
LOG_DEBUG("Installing SIGTERM handler ...");
US_LOG_DEBUG("Installing SIGTERM handler ...");
assert(!sigaction(SIGTERM, &sig_act, NULL));
LOG_DEBUG("Installing SIGTERM handler ...");
US_LOG_DEBUG("Installing SIGTERM handler ...");
assert(!sigaction(SIGPIPE, &sig_act, NULL));
}
static int _dump_sink(const char *sink_name, unsigned sink_timeout, const char *output_path, bool output_json) {
frame_s *frame = frame_init("input");
memsink_s *sink = NULL;
FILE *output_fp = NULL;
char *base64_data = NULL;
size_t base64_allocated = 0;
static int _dump_sink(
const char *sink_name, unsigned sink_timeout,
long long count, long double interval,
_output_context_s *ctx) {
if (output_path && output_path[0] != '\0') {
if (!strcmp(output_path, "-")) {
LOG_INFO("Using output: <stdout>");
output_fp = stdout;
} else {
LOG_INFO("Using output: %s", output_path);
if ((output_fp = fopen(output_path, "wb")) == NULL) {
LOG_PERROR("Can't open output file");
goto error;
}
}
if (count == 0) {
count = -1;
}
if ((sink = memsink_init("input", sink_name, false, 0, false, sink_timeout)) == NULL) {
const useconds_t interval_us = interval * 1000000;
us_frame_s *frame = us_frame_init();
us_memsink_s *sink = NULL;
if ((sink = us_memsink_init("input", sink_name, false, 0, false, 0, sink_timeout)) == NULL) {
goto error;
}
@@ -201,48 +242,50 @@ static int _dump_sink(const char *sink_name, unsigned sink_timeout, const char *
unsigned fps_accum = 0;
long long fps_second = 0;
while (!stop) {
int error = memsink_client_get(sink, frame);
long double last_ts = 0;
while (!_g_stop) {
int error = us_memsink_client_get(sink, frame);
if (error == 0) {
const long double now = get_now_monotonic();
const long long now_second = floor_ms(now);
const long double now = us_get_now_monotonic();
const long long now_second = us_floor_ms(now);
char fourcc_str[8];
LOG_VERBOSE("Frame: size=%zu, resolution=%ux%u, fourcc=%s, stride=%u, online=%d, latency=%.3Lf",
US_LOG_VERBOSE("Frame: size=%zu, res=%ux%u, fourcc=%s, stride=%u, online=%d, key=%d, latency=%.3Lf, diff=%.3Lf",
frame->used, frame->width, frame->height,
fourcc_to_string(frame->format, fourcc_str, 8),
frame->stride, frame->online,
now - frame->grab_ts);
us_fourcc_to_string(frame->format, fourcc_str, 8),
frame->stride, frame->online, frame->key,
now - frame->grab_ts, (last_ts ? now - last_ts : 0));
last_ts = now;
LOG_DEBUG(" grab_ts=%.3Lf, encode_begin_ts=%.3Lf, encode_end_ts=%.3Lf",
US_LOG_DEBUG(" grab_ts=%.3Lf, encode_begin_ts=%.3Lf, encode_end_ts=%.3Lf",
frame->grab_ts, frame->encode_begin_ts, frame->encode_end_ts);
if (now_second != fps_second) {
fps = fps_accum;
fps_accum = 0;
fps_second = now_second;
LOG_PERF_FPS("A new second has come; captured_fps=%u", fps);
US_LOG_PERF_FPS("A new second has come; captured_fps=%u", fps);
}
fps_accum += 1;
if (output_fp) {
if (output_json) {
base64_encode(frame->data, frame->used, &base64_data, &base64_allocated);
fprintf(output_fp,
"{\"size\": %zu, \"width\": %u, \"height\": %u,"
" \"format\": %u, \"stride\": %u, \"online\": %u,"
" \"grab_ts\": %.3Lf, \"encode_begin_ts\": %.3Lf, \"encode_end_ts\": %.3Lf,"
" \"data\": \"%s\"}\n",
frame->used, frame->width, frame->height,
frame->format, frame->stride, frame->online,
frame->grab_ts, frame->encode_begin_ts, frame->encode_end_ts,
base64_data);
} else {
fwrite(frame->data, 1, frame->used, output_fp);
}
fflush(output_fp);
if (ctx->v_output != NULL) {
ctx->write(ctx->v_output, frame);
}
} else if (error != -2) {
if (count >= 0) {
--count;
if (count <= 0) {
break;
}
}
if (interval_us > 0) {
usleep(interval_us);
}
} else if (error == -2) {
usleep(1000);
} else {
goto error;
}
}
@@ -254,20 +297,10 @@ static int _dump_sink(const char *sink_name, unsigned sink_timeout, const char *
retval = -1;
ok:
if (base64_data) {
free(base64_data);
}
if (output_fp && output_fp != stdout) {
if (fclose(output_fp) < 0) {
LOG_PERROR("Can't close output file");
}
}
if (sink) {
memsink_destroy(sink);
}
frame_destroy(frame);
US_DELETE(sink, us_memsink_destroy);
us_frame_destroy(frame);
LOG_INFO("Bye-bye");
US_LOG_INFO("Bye-bye");
return retval;
}
@@ -275,8 +308,8 @@ static void _help(FILE *fp) {
# define SAY(_msg, ...) fprintf(fp, _msg "\n", ##__VA_ARGS__)
SAY("\nuStreamer-dump - Dump uStreamer's memory sink to file");
SAY("═════════════════════════════════════════════════════");
SAY("Version: %s; license: GPLv3", VERSION);
SAY("Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com>\n");
SAY("Version: %s; license: GPLv3", US_VERSION);
SAY("Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com>\n");
SAY("Example:");
SAY("════════");
SAY(" ustreamer-dump --sink test --output - \\");
@@ -287,12 +320,14 @@ static void _help(FILE *fp) {
SAY(" -t|--sink-timeout <sec> ─ Timeout for the upcoming frame. Default: 1.\n");
SAY(" -o|--output <filename> ─── Filename to dump output to. Use '-' for stdout. Default: just consume the sink.\n");
SAY(" -j|--output-json ──────── Format output as JSON. Required option --output. Default: disabled.\n");
SAY(" -c|--count <N> ───────── Limit the number of frames. Default: 0 (infinite).\n");
SAY(" -i|--interval <sec> ───── Delay between reading frames (float). Default: 0.\n");
SAY("Logging options:");
SAY("════════════════");
SAY(" --log-level <N> ──── Verbosity level of messages from 0 (info) to 3 (debug).");
SAY(" Enabling debugging messages can slow down the program.");
SAY(" Available levels: 0 (info), 1 (performance), 2 (verbose), 3 (debug).");
SAY(" Default: %d.\n", log_level);
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");

37
src/libs/array.h Normal file
View File

@@ -0,0 +1,37 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <assert.h>
#define US_ARRAY_LEN(x_array) (sizeof(x_array) / sizeof((x_array)[0]))
#define US_ARRAY_ITERATE(x_array, x_start, x_item_ptr, ...) { \
const int m_len = US_ARRAY_LEN(x_array); \
assert(x_start <= m_len); \
for (int m_index = x_start; m_index < m_len; ++m_index) { \
__typeof__((x_array)[0]) *const x_item_ptr = &x_array[m_index]; \
__VA_ARGS__ \
} \
}

View File

@@ -1,8 +1,8 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -37,11 +37,11 @@ static const char _ENCODING_TABLE[] = {
static const unsigned _MOD_TABLE[] = {0, 2, 1};
void base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *allocated) {
void us_base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *allocated) {
const size_t encoded_size = 4 * ((size + 2) / 3) + 1; // +1 for '\0'
if (*encoded == NULL || (allocated && *allocated < encoded_size)) {
A_REALLOC(*encoded, encoded_size);
US_REALLOC(*encoded, encoded_size);
if (allocated) {
*allocated = encoded_size;
}
@@ -54,7 +54,7 @@ void base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *all
OCTET(octet_c);
# undef OCTET
unsigned triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
const unsigned triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
# define ENCODE(_offset) (*encoded)[encoded_index++] = _ENCODING_TABLE[(triple >> _offset * 6) & 0x3F]
ENCODE(3);

View File

@@ -1,8 +1,8 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -31,4 +31,4 @@
#include "tools.h"
void base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *allocated);
void us_base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *allocated);

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

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

View File

@@ -1,8 +1,8 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -23,87 +23,53 @@
#include "frame.h"
frame_s *frame_init(const char *name) {
frame_s *frame;
A_CALLOC(frame, 1);
frame->name = name;
frame->managed = true;
frame_realloc_data(frame, 512 * 1024);
us_frame_s *us_frame_init(void) {
us_frame_s *frame;
US_CALLOC(frame, 1);
us_frame_realloc_data(frame, 512 * 1024);
frame->dma_fd = -1;
return frame;
}
void frame_destroy(frame_s *frame) {
assert(frame->managed);
if (frame->data) {
free(frame->data);
}
void us_frame_destroy(us_frame_s *frame) {
US_DELETE(frame->data, free);
free(frame);
}
void frame_realloc_data(frame_s *frame, size_t size) {
assert(frame->managed);
void us_frame_realloc_data(us_frame_s *frame, size_t size) {
if (frame->allocated < size) {
LOG_DEBUG("Increasing frame buffer '%s': %zu -> %zu (+%zu)",
frame->name, frame->allocated, size, size - frame->allocated);
A_REALLOC(frame->data, size);
US_REALLOC(frame->data, size);
frame->allocated = size;
}
}
void frame_set_data(frame_s *frame, const uint8_t *data, size_t size) {
assert(frame->managed);
frame_realloc_data(frame, size);
void us_frame_set_data(us_frame_s *frame, const uint8_t *data, size_t size) {
us_frame_realloc_data(frame, size);
memcpy(frame->data, data, size);
frame->used = size;
}
void frame_append_data(frame_s *frame, const uint8_t *data, size_t size) {
assert(frame->managed);
size_t new_used = frame->used + size;
frame_realloc_data(frame, new_used);
void us_frame_append_data(us_frame_s *frame, const uint8_t *data, size_t size) {
const size_t new_used = frame->used + size;
us_frame_realloc_data(frame, new_used);
memcpy(frame->data + frame->used, data, size);
frame->used = new_used;
}
#define COPY(_field) dest->_field = src->_field
void frame_copy(const frame_s *src, frame_s *dest) {
assert(dest->managed);
frame_set_data(dest, src->data, src->used);
COPY(used);
frame_copy_meta(src, dest);
void us_frame_copy(const us_frame_s *src, us_frame_s *dest) {
us_frame_set_data(dest, src->data, src->used);
US_FRAME_COPY_META(src, dest);
}
void frame_copy_meta(const frame_s *src, frame_s *dest) {
// Don't copy the name
COPY(width);
COPY(height);
COPY(format);
COPY(stride);
COPY(online);
COPY(grab_ts);
COPY(encode_begin_ts);
COPY(encode_end_ts);
}
#undef COPY
bool frame_compare(const frame_s *a, const frame_s *b) {
# define CMP(_field) (a->_field == b->_field)
bool us_frame_compare(const us_frame_s *a, const us_frame_s *b) {
return (
a->allocated && b->allocated
&& CMP(used)
&& CMP(width)
&& CMP(height)
&& CMP(format)
&& CMP(stride)
&& CMP(online)
&& US_FRAME_COMPARE_META_USED_NOTS(a, b)
&& !memcmp(a->data, b->data, b->used)
);
# undef CMP
}
unsigned frame_get_padding(const frame_s *frame) {
unsigned us_frame_get_padding(const us_frame_s *frame) {
unsigned bytes_per_pixel = 0;
switch (frame->format) {
case V4L2_PIX_FMT_YUYV:
@@ -113,7 +79,7 @@ unsigned frame_get_padding(const frame_s *frame) {
// case V4L2_PIX_FMT_H264:
case V4L2_PIX_FMT_MJPEG:
case V4L2_PIX_FMT_JPEG: bytes_per_pixel = 0; break;
default: assert(0 && "Unknown pixelformat");
default: assert(0 && "Unknown format");
}
if (bytes_per_pixel > 0 && frame->stride > frame->width) {
return (frame->stride - frame->width * bytes_per_pixel);
@@ -121,7 +87,7 @@ unsigned frame_get_padding(const frame_s *frame) {
return 0;
}
const char *fourcc_to_string(unsigned format, char *buf, size_t size) {
const char *us_fourcc_to_string(unsigned format, char *buf, size_t size) {
assert(size >= 8);
buf[0] = format & 0x7F;
buf[1] = (format >> 8) & 0x7F;

View File

@@ -1,8 +1,8 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -32,49 +32,87 @@
#include <linux/videodev2.h>
#include "tools.h"
#include "logging.h"
typedef struct {
const char *name;
uint8_t *data;
size_t used;
size_t allocated;
int dma_fd;
uint8_t *data;
size_t used;
size_t allocated;
unsigned width;
unsigned height;
unsigned format;
unsigned stride;
unsigned width;
unsigned height;
unsigned format;
unsigned stride;
// Stride is a bytesperline in V4L2
// https://www.kernel.org/doc/html/v4.14/media/uapi/v4l/pixfmt-v4l2.html
// https://medium.com/@oleg.shipitko/what-does-stride-mean-in-image-processing-bba158a72bcd
bool online;
bool online;
bool key;
long double grab_ts;
long double encode_begin_ts;
long double encode_end_ts;
bool managed;
} frame_s;
} us_frame_s;
frame_s *frame_init(const char *name);
void frame_destroy(frame_s *frame);
#define US_FRAME_COPY_META(x_src, x_dest) { \
x_dest->width = x_src->width; \
x_dest->height = x_src->height; \
x_dest->format = x_src->format; \
x_dest->stride = x_src->stride; \
x_dest->online = x_src->online; \
x_dest->key = x_src->key; \
x_dest->grab_ts = x_src->grab_ts; \
x_dest->encode_begin_ts = x_src->encode_begin_ts; \
x_dest->encode_end_ts = x_src->encode_end_ts; \
}
void frame_realloc_data(frame_s *frame, size_t size);
void frame_set_data(frame_s *frame, const uint8_t *data, size_t size);
void frame_append_data(frame_s *frame, const uint8_t *data, size_t size);
static inline void us_frame_copy_meta(const us_frame_s *src, us_frame_s *dest) {
US_FRAME_COPY_META(src, dest);
}
void frame_copy(const frame_s *src, frame_s *dest);
void frame_copy_meta(const frame_s *src, frame_s *dest);
bool frame_compare(const frame_s *a, const frame_s *b);
#define US_FRAME_COMPARE_META_USED_NOTS(x_a, x_b) ( \
x_a->used == x_b->used \
&& x_a->width == x_b->width \
&& x_a->height == x_b->height \
&& x_a->format == x_b->format \
&& x_a->stride == x_b->stride \
&& x_a->online == x_b->online \
&& x_a->key == x_b->key \
)
unsigned frame_get_padding(const frame_s *frame);
const char *fourcc_to_string(unsigned format, char *buf, size_t size);
static inline void us_frame_encoding_begin(const us_frame_s *src, us_frame_s *dest, unsigned format) {
assert(src->used > 0);
us_frame_copy_meta(src, dest);
dest->encode_begin_ts = us_get_now_monotonic();
dest->format = format;
dest->stride = 0;
dest->used = 0;
}
inline bool is_jpeg(unsigned format) {
static inline void us_frame_encoding_end(us_frame_s *dest) {
assert(dest->used > 0);
dest->encode_end_ts = us_get_now_monotonic();
}
us_frame_s *us_frame_init(void);
void us_frame_destroy(us_frame_s *frame);
void us_frame_realloc_data(us_frame_s *frame, size_t size);
void us_frame_set_data(us_frame_s *frame, const uint8_t *data, size_t size);
void us_frame_append_data(us_frame_s *frame, const uint8_t *data, size_t size);
void us_frame_copy(const us_frame_s *src, us_frame_s *dest);
bool us_frame_compare(const us_frame_s *a, const us_frame_s *b);
unsigned us_frame_get_padding(const us_frame_s *frame);
const char *us_fourcc_to_string(unsigned format, char *buf, size_t size);
static inline bool us_is_jpeg(unsigned format) {
return (format == V4L2_PIX_FMT_JPEG || format == V4L2_PIX_FMT_MJPEG);
}

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

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

View File

@@ -1,8 +1,8 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -23,8 +23,8 @@
#include "logging.h"
enum log_level_t log_level;
enum us_log_level_t us_g_log_level;
bool log_colored;
bool us_g_log_colored;
pthread_mutex_t log_mutex;
pthread_mutex_t us_g_log_mutex;

View File

@@ -1,8 +1,8 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -37,136 +37,126 @@
#include "threading.h"
enum log_level_t {
LOG_LEVEL_INFO,
LOG_LEVEL_PERF,
LOG_LEVEL_VERBOSE,
LOG_LEVEL_DEBUG,
enum us_log_level_t {
US_LOG_LEVEL_INFO,
US_LOG_LEVEL_PERF,
US_LOG_LEVEL_VERBOSE,
US_LOG_LEVEL_DEBUG,
};
extern enum log_level_t log_level;
extern enum us_log_level_t us_g_log_level;
extern bool log_colored;
extern bool us_g_log_colored;
extern pthread_mutex_t log_mutex;
extern pthread_mutex_t us_g_log_mutex;
#define LOGGING_INIT { \
log_level = LOG_LEVEL_INFO; \
log_colored = isatty(2); \
A_MUTEX_INIT(&log_mutex); \
#define US_LOGGING_INIT { \
us_g_log_level = US_LOG_LEVEL_INFO; \
us_g_log_colored = isatty(2); \
US_MUTEX_INIT(us_g_log_mutex); \
}
#define LOGGING_DESTROY A_MUTEX_DESTROY(&log_mutex)
#define US_LOGGING_DESTROY US_MUTEX_DESTROY(us_g_log_mutex)
#define LOGGING_LOCK A_MUTEX_LOCK(&log_mutex)
#define LOGGING_UNLOCK A_MUTEX_UNLOCK(&log_mutex)
#define US_LOGGING_LOCK US_MUTEX_LOCK(us_g_log_mutex)
#define US_LOGGING_UNLOCK US_MUTEX_UNLOCK(us_g_log_mutex)
#define COLOR_GRAY "\x1b[30;1m"
#define COLOR_RED "\x1b[31;1m"
#define COLOR_GREEN "\x1b[32;1m"
#define COLOR_YELLOW "\x1b[33;1m"
#define COLOR_BLUE "\x1b[34;1m"
#define COLOR_CYAN "\x1b[36;1m"
#define COLOR_RESET "\x1b[0m"
#define US_COLOR_GRAY "\x1b[30;1m"
#define US_COLOR_RED "\x1b[31;1m"
#define US_COLOR_GREEN "\x1b[32;1m"
#define US_COLOR_YELLOW "\x1b[33;1m"
#define US_COLOR_BLUE "\x1b[34;1m"
#define US_COLOR_CYAN "\x1b[36;1m"
#define US_COLOR_RESET "\x1b[0m"
#define SEP_INFO(_ch) { \
LOGGING_LOCK; \
for (int _i = 0; _i < 80; ++_i) { \
fputc(_ch, stderr); \
#define US_SEP_INFO(x_ch) { \
US_LOGGING_LOCK; \
for (int m_count = 0; m_count < 80; ++m_count) { \
fputc((x_ch), stderr); \
} \
fputc('\n', stderr); \
fflush(stderr); \
LOGGING_UNLOCK; \
US_LOGGING_UNLOCK; \
}
#define SEP_DEBUG(_ch) { \
if (log_level >= LOG_LEVEL_DEBUG) { \
SEP_INFO(_ch); \
#define US_SEP_DEBUG(x_ch) { \
if (us_g_log_level >= US_LOG_LEVEL_DEBUG) { \
US_SEP_INFO(x_ch); \
} \
}
#define LOG_PRINTF_NOLOCK(_label_color, _label, _msg_color, _msg, ...) { \
char _tname_buf[MAX_THREAD_NAME] = {0}; \
thread_get_name(_tname_buf); \
if (log_colored) { \
fprintf(stderr, COLOR_GRAY "-- " _label_color _label COLOR_GRAY \
" [%.03Lf %9s]" " -- " COLOR_RESET _msg_color _msg COLOR_RESET, \
get_now_monotonic(), _tname_buf, ##__VA_ARGS__); \
#define US_LOG_PRINTF_NOLOCK(x_label_color, x_label, x_msg_color, x_msg, ...) { \
char m_tname_buf[US_MAX_THREAD_NAME] = {0}; \
us_thread_get_name(m_tname_buf); \
if (us_g_log_colored) { \
fprintf(stderr, US_COLOR_GRAY "-- " x_label_color x_label US_COLOR_GRAY \
" [%.03Lf %9s]" " -- " US_COLOR_RESET x_msg_color x_msg US_COLOR_RESET, \
us_get_now_monotonic(), m_tname_buf, ##__VA_ARGS__); \
} else { \
fprintf(stderr, "-- " _label " [%.03Lf %9s] -- " _msg, \
get_now_monotonic(), _tname_buf, ##__VA_ARGS__); \
fprintf(stderr, "-- " x_label " [%.03Lf %9s] -- " x_msg, \
us_get_now_monotonic(), m_tname_buf, ##__VA_ARGS__); \
} \
fputc('\n', stderr); \
fflush(stderr); \
}
#define LOG_PRINTF(_label_color, _label, _msg_color, _msg, ...) { \
LOGGING_LOCK; \
LOG_PRINTF_NOLOCK(_label_color, _label, _msg_color, _msg, ##__VA_ARGS__); \
LOGGING_UNLOCK; \
#define US_LOG_PRINTF(x_label_color, x_label, x_msg_color, x_msg, ...) { \
US_LOGGING_LOCK; \
US_LOG_PRINTF_NOLOCK(x_label_color, x_label, x_msg_color, x_msg, ##__VA_ARGS__); \
US_LOGGING_UNLOCK; \
}
#define LOG_ERROR(_msg, ...) { \
LOG_PRINTF(COLOR_RED, "ERROR", COLOR_RED, _msg, ##__VA_ARGS__); \
#define US_LOG_ERROR(x_msg, ...) { \
US_LOG_PRINTF(US_COLOR_RED, "ERROR", US_COLOR_RED, x_msg, ##__VA_ARGS__); \
}
#define LOG_PERROR(_msg, ...) { \
char _perror_buf[1024] = {0}; \
char *_perror_ptr = errno_to_string(errno, _perror_buf, 1024); \
LOG_ERROR(_msg ": %s", ##__VA_ARGS__, _perror_ptr); \
#define US_LOG_PERROR(x_msg, ...) { \
char *const m_perror_str = us_errno_to_string(errno); \
US_LOG_ERROR(x_msg ": %s", ##__VA_ARGS__, m_perror_str); \
free(m_perror_str); \
}
#define LOG_INFO(_msg, ...) { \
LOG_PRINTF(COLOR_GREEN, "INFO ", "", _msg, ##__VA_ARGS__); \
#define US_LOG_INFO(x_msg, ...) { \
US_LOG_PRINTF(US_COLOR_GREEN, "INFO ", "", x_msg, ##__VA_ARGS__); \
}
#define LOG_INFO_NOLOCK(_msg, ...) { \
LOG_PRINTF_NOLOCK(COLOR_GREEN, "INFO ", "", _msg, ##__VA_ARGS__); \
#define US_LOG_INFO_NOLOCK(x_msg, ...) { \
US_LOG_PRINTF_NOLOCK(US_COLOR_GREEN, "INFO ", "", x_msg, ##__VA_ARGS__); \
}
#define LOG_PERF(_msg, ...) { \
if (log_level >= LOG_LEVEL_PERF) { \
LOG_PRINTF(COLOR_CYAN, "PERF ", COLOR_CYAN, _msg, ##__VA_ARGS__); \
#define US_LOG_PERF(x_msg, ...) { \
if (us_g_log_level >= US_LOG_LEVEL_PERF) { \
US_LOG_PRINTF(US_COLOR_CYAN, "PERF ", US_COLOR_CYAN, x_msg, ##__VA_ARGS__); \
} \
}
#define LOG_PERF_FPS(_msg, ...) { \
if (log_level >= LOG_LEVEL_PERF) { \
LOG_PRINTF(COLOR_YELLOW, "PERF ", COLOR_YELLOW, _msg, ##__VA_ARGS__); \
#define US_LOG_PERF_FPS(x_msg, ...) { \
if (us_g_log_level >= US_LOG_LEVEL_PERF) { \
US_LOG_PRINTF(US_COLOR_YELLOW, "PERF ", US_COLOR_YELLOW, x_msg, ##__VA_ARGS__); \
} \
}
#define LOG_VERBOSE(_msg, ...) { \
if (log_level >= LOG_LEVEL_VERBOSE) { \
LOG_PRINTF(COLOR_BLUE, "VERB ", COLOR_BLUE, _msg, ##__VA_ARGS__); \
#define US_LOG_VERBOSE(x_msg, ...) { \
if (us_g_log_level >= US_LOG_LEVEL_VERBOSE) { \
US_LOG_PRINTF(US_COLOR_BLUE, "VERB ", US_COLOR_BLUE, x_msg, ##__VA_ARGS__); \
} \
}
#define LOG_VERBOSE_PERROR(_msg, ...) { \
if (log_level >= LOG_LEVEL_VERBOSE) { \
char _perror_buf[1024] = {0}; \
char *_perror_ptr = errno_to_string(errno, _perror_buf, 1024); \
LOG_PRINTF(COLOR_BLUE, "VERB ", COLOR_BLUE, _msg ": %s", ##__VA_ARGS__, _perror_ptr); \
#define US_LOG_VERBOSE_PERROR(x_msg, ...) { \
if (us_g_log_level >= US_LOG_LEVEL_VERBOSE) { \
char *m_perror_str = us_errno_to_string(errno); \
US_LOG_PRINTF(US_COLOR_BLUE, "VERB ", US_COLOR_BLUE, x_msg ": %s", ##__VA_ARGS__, m_perror_str); \
free(m_perror_str); \
} \
}
#define LOG_DEBUG(_msg, ...) { \
if (log_level >= LOG_LEVEL_DEBUG) { \
LOG_PRINTF(COLOR_GRAY, "DEBUG", COLOR_GRAY, _msg, ##__VA_ARGS__); \
#define US_LOG_DEBUG(x_msg, ...) { \
if (us_g_log_level >= US_LOG_LEVEL_DEBUG) { \
US_LOG_PRINTF(US_COLOR_GRAY, "DEBUG", US_COLOR_GRAY, x_msg, ##__VA_ARGS__); \
} \
}
INLINE char *errno_to_string(int error, char *buf, size_t size) {
# if defined(__GLIBC__) && defined(_GNU_SOURCE)
return strerror_r(error, buf, size);
# else
strerror_r(error, buf, size);
return buf;
# endif
}

View File

@@ -1,8 +1,8 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -23,170 +23,174 @@
#include "memsink.h"
static int _flock_timedwait_monotonic(int fd, long double timeout);
us_memsink_s *us_memsink_init(
const char *name, const char *obj, bool server,
mode_t mode, bool rm, unsigned client_ttl, unsigned timeout) {
memsink_s *memsink_init(const char *name, const char *obj, bool server, mode_t mode, bool rm, unsigned timeout) {
memsink_s *sink;
A_CALLOC(sink, 1);
us_memsink_s *sink;
US_CALLOC(sink, 1);
sink->name = name;
sink->obj = obj;
sink->server = server;
sink->rm = rm;
sink->client_ttl = client_ttl;
sink->timeout = timeout;
sink->fd = -1;
sink->mem = MAP_FAILED;
atomic_init(&sink->has_clients, false);
LOG_INFO("Using %s-sink: %s", name, obj);
US_LOG_INFO("Using %s-sink: %s", name, obj);
if ((sink->fd = shm_open(sink->obj, (server ? O_RDWR | O_CREAT : O_RDWR), mode)) == -1) {
LOG_PERROR("%s-sink: Can't open shared memory", name);
const mode_t mask = umask(0);
sink->fd = shm_open(sink->obj, (server ? O_RDWR | O_CREAT : O_RDWR), mode);
umask(mask);
if (sink->fd == -1) {
umask(mask);
US_LOG_PERROR("%s-sink: Can't open shared memory", name);
goto error;
}
if (sink->server && ftruncate(sink->fd, sizeof(memsink_shared_s)) < 0) {
LOG_PERROR("%s-sink: Can't truncate shared memory", name);
if (sink->server && ftruncate(sink->fd, sizeof(us_memsink_shared_s)) < 0) {
US_LOG_PERROR("%s-sink: Can't truncate shared memory", name);
goto error;
}
if ((sink->mem = mmap(
NULL,
sizeof(memsink_shared_s),
PROT_READ | PROT_WRITE,
MAP_SHARED,
sink->fd,
0
)) == MAP_FAILED) {
LOG_PERROR("%s-sink: Can't mmap shared memory", name);
if ((sink->mem = us_memsink_shared_map(sink->fd)) == NULL) {
US_LOG_PERROR("%s-sink: Can't mmap shared memory", name);
goto error;
}
return sink;
error:
memsink_destroy(sink);
us_memsink_destroy(sink);
return NULL;
}
void memsink_destroy(memsink_s *sink) {
if (sink->mem != MAP_FAILED) {
if (munmap(sink->mem, sizeof(memsink_shared_s)) < 0) {
LOG_PERROR("%s-sink: Can't unmap shared memory", sink->name);
void us_memsink_destroy(us_memsink_s *sink) {
if (sink->mem != NULL) {
if (us_memsink_shared_unmap(sink->mem) < 0) {
US_LOG_PERROR("%s-sink: Can't unmap shared memory", sink->name);
}
}
if (sink->fd >= 0) {
if (close(sink->fd) < 0) {
LOG_PERROR("%s-sink: Can't close shared memory fd", sink->name);
US_LOG_PERROR("%s-sink: Can't close shared memory fd", sink->name);
}
if (sink->rm && shm_unlink(sink->obj) < 0) {
if (errno != ENOENT) {
LOG_PERROR("%s-sink: Can't remove shared memory", sink->name);
US_LOG_PERROR("%s-sink: Can't remove shared memory", sink->name);
}
}
}
free(sink);
}
int memsink_server_put(memsink_s *sink, const frame_s *frame) {
bool us_memsink_server_check(us_memsink_s *sink, const us_frame_s *frame) {
// Return true (the need to write to memsink) on any of these conditions:
// - EWOULDBLOCK - we have an active client;
// - Incorrect magic or version - need to first write;
// - We have some active clients by last_client_ts;
// - Frame meta differs (like size, format, but not timestamp).
assert(sink->server);
const long double now = get_now_monotonic();
if (flock(sink->fd, LOCK_EX | LOCK_NB) < 0) {
if (errno == EWOULDBLOCK) {
atomic_store(&sink->has_clients, true);
return true;
}
US_LOG_PERROR("%s-sink: Can't lock memory", sink->name);
return false;
}
if (frame->used > MEMSINK_MAX_DATA) {
LOG_ERROR("%s-sink: Can't put frame: is too big (%zu > %zu)",
sink->name, frame->used, MEMSINK_MAX_DATA);
if (sink->mem->magic != US_MEMSINK_MAGIC || sink->mem->version != US_MEMSINK_VERSION) {
return true;
}
const bool has_clients = (sink->mem->last_client_ts + sink->client_ttl > us_get_now_monotonic());
atomic_store(&sink->has_clients, has_clients);
if (flock(sink->fd, LOCK_UN) < 0) {
US_LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
return false;
}
return (has_clients || !US_FRAME_COMPARE_META_USED_NOTS(sink->mem, frame));;
}
int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame) {
assert(sink->server);
const long double now = us_get_now_monotonic();
if (frame->used > US_MEMSINK_MAX_DATA) {
US_LOG_ERROR("%s-sink: Can't put frame: is too big (%zu > %zu)",
sink->name, frame->used, US_MEMSINK_MAX_DATA);
return 0; // -2
}
if (_flock_timedwait_monotonic(sink->fd, 1) == 0) {
LOG_VERBOSE("%s-sink: >>>>> Exposing new frame ...", sink->name);
if (us_flock_timedwait_monotonic(sink->fd, 1) == 0) {
US_LOG_VERBOSE("%s-sink: >>>>> Exposing new frame ...", sink->name);
# define COPY(_field) sink->mem->_field = frame->_field
sink->last_id = get_now_id();
sink->last_id = us_get_now_id();
sink->mem->id = sink->last_id;
COPY(used);
COPY(width);
COPY(height);
COPY(format);
COPY(stride);
COPY(online);
COPY(grab_ts);
COPY(encode_begin_ts);
COPY(encode_end_ts);
sink->has_clients = (sink->mem->last_consumed_ts + 10 > get_now_monotonic());
memcpy(sink->mem->data, frame->data, frame->used);
# undef COPY
sink->mem->used = frame->used;
US_FRAME_COPY_META(frame, sink->mem);
sink->mem->magic = US_MEMSINK_MAGIC;
sink->mem->version = US_MEMSINK_VERSION;
atomic_store(&sink->has_clients, (sink->mem->last_client_ts + sink->client_ttl > us_get_now_monotonic()));
if (flock(sink->fd, LOCK_UN) < 0) {
LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
US_LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
return -1;
}
LOG_VERBOSE("%s-sink: Exposed new frame; full exposition time = %.3Lf",
sink->name, get_now_monotonic() - now);
US_LOG_VERBOSE("%s-sink: Exposed new frame; full exposition time = %.3Lf",
sink->name, us_get_now_monotonic() - now);
} else if (errno == EWOULDBLOCK) {
LOG_VERBOSE("%s-sink: ===== Shared memory is busy now; frame skipped", sink->name);
US_LOG_VERBOSE("%s-sink: ===== Shared memory is busy now; frame skipped", sink->name);
} else {
LOG_PERROR("%s-sink: Can't lock memory", sink->name);
US_LOG_PERROR("%s-sink: Can't lock memory", sink->name);
return -1;
}
return 0;
}
int memsink_client_get(memsink_s *sink, frame_s *frame) { // cppcheck-suppress unusedFunction
int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame) { // cppcheck-suppress unusedFunction
assert(!sink->server); // Client only
if (_flock_timedwait_monotonic(sink->fd, sink->timeout) < 0) {
if (us_flock_timedwait_monotonic(sink->fd, sink->timeout) < 0) {
if (errno == EWOULDBLOCK) {
return -2;
}
LOG_PERROR("%s-sink: Can't lock memory", sink->name);
US_LOG_PERROR("%s-sink: Can't lock memory", sink->name);
return -1;
}
bool same = false;
if (sink->mem->id == sink->last_id) {
same = true;
} else {
# define COPY(_field) frame->_field = sink->mem->_field
sink->last_id = sink->mem->id;
COPY(width);
COPY(height);
COPY(format);
COPY(stride);
COPY(online);
COPY(grab_ts);
COPY(encode_begin_ts);
COPY(encode_end_ts);
sink->mem->last_consumed_ts = get_now_monotonic();
frame_set_data(frame, sink->mem->data, sink->mem->used);
# undef COPY
}
if (flock(sink->fd, LOCK_UN) < 0) {
LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
return -1;
}
if (same) {
usleep(1000);
return -2;
}
return 0;
}
static int _flock_timedwait_monotonic(int fd, long double timeout) {
long double deadline_ts = get_now_monotonic() + timeout;
int retval = -1;
while (true) {
retval = flock(fd, LOCK_EX | LOCK_NB);
if (retval == 0 || errno != EWOULDBLOCK || get_now_monotonic() > deadline_ts) {
break;
int retval = -2; // Not updated
if (sink->mem->magic == US_MEMSINK_MAGIC) {
if (sink->mem->version != US_MEMSINK_VERSION) {
US_LOG_ERROR("%s-sink: Protocol version mismatch: sink=%u, required=%u",
sink->name, sink->mem->version, US_MEMSINK_VERSION);
retval = -1;
goto done;
}
usleep(1000);
if (sink->mem->id != sink->last_id) { // When updated
sink->last_id = sink->mem->id;
us_frame_set_data(frame, sink->mem->data, sink->mem->used);
US_FRAME_COPY_META(sink->mem, frame);
retval = 0;
}
sink->mem->last_client_ts = us_get_now_monotonic();
}
return retval;
done:
if (flock(sink->fd, LOCK_UN) < 0) {
US_LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
return -1;
}
return retval;
}

View File

@@ -1,8 +1,8 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -24,6 +24,7 @@
#include <stdint.h>
#include <stdbool.h>
#include <stdatomic.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
@@ -37,45 +38,31 @@
#include "tools.h"
#include "logging.h"
#include "frame.h"
#ifndef CFG_MEMSINK_MAX_DATA
# define CFG_MEMSINK_MAX_DATA 33554432
#endif
#define MEMSINK_MAX_DATA ((size_t)(CFG_MEMSINK_MAX_DATA))
#include "memsinksh.h"
typedef struct {
uint64_t id;
size_t used;
unsigned width;
unsigned height;
unsigned format;
unsigned stride;
bool online;
long double grab_ts;
long double encode_begin_ts;
long double encode_end_ts;
long double last_consumed_ts;
uint8_t data[MEMSINK_MAX_DATA];
} memsink_shared_s;
typedef struct {
const char *name;
const char *obj;
bool server;
bool rm;
unsigned timeout;
const char *name;
const char *obj;
bool server;
bool rm;
unsigned client_ttl; // Only for server
unsigned timeout;
int fd;
memsink_shared_s *mem;
us_memsink_shared_s *mem;
uint64_t last_id;
bool has_clients; // Only for server
} memsink_s;
atomic_bool has_clients; // Only for server
} us_memsink_s;
memsink_s *memsink_init(const char *name, const char *obj, bool server, mode_t mode, bool rm, unsigned timeout);
void memsink_destroy(memsink_s *sink);
us_memsink_s *us_memsink_init(
const char *name, const char *obj, bool server,
mode_t mode, bool rm, unsigned client_ttl, unsigned timeout);
int memsink_server_put(memsink_s *sink, const frame_s *frame);
int memsink_client_get(memsink_s *sink, frame_s *frame);
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);
int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame);

View File

@@ -1,8 +1,8 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -22,51 +22,63 @@
#pragma once
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <linux/videodev2.h>
#include <IL/OMX_Core.h>
#include <IL/OMX_Component.h>
#include <IL/OMX_Broadcom.h>
#include <interface/vcos/vcos_semaphore.h>
#include "../../../libs/tools.h"
#include "../../../libs/logging.h"
#include "../../../libs/frame.h"
#include "formatters.h"
#include "component.h"
#include <sys/types.h>
#include <sys/mman.h>
#ifndef CFG_OMX_MAX_ENCODERS
# define CFG_OMX_MAX_ENCODERS 3 // Raspberry Pi limitation
#define US_MEMSINK_MAGIC ((uint64_t)0xCAFEBABECAFEBABE)
#define US_MEMSINK_VERSION ((uint32_t)2)
#ifndef US_CFG_MEMSINK_MAX_DATA
# define US_CFG_MEMSINK_MAX_DATA 33554432
#endif
#define OMX_MAX_ENCODERS ((unsigned)(CFG_OMX_MAX_ENCODERS))
#define US_MEMSINK_MAX_DATA ((size_t)(US_CFG_MEMSINK_MAX_DATA))
typedef struct {
OMX_HANDLETYPE comp;
OMX_BUFFERHEADERTYPE *input_buf;
OMX_BUFFERHEADERTYPE *output_buf;
bool input_required;
bool output_available;
bool failed;
VCOS_SEMAPHORE_T handler_sem;
uint64_t magic;
uint32_t version;
bool i_handler_sem;
bool i_encoder;
bool i_input_port_enabled;
bool i_output_port_enabled;
} omx_encoder_s;
uint64_t id;
size_t used;
unsigned width;
unsigned height;
unsigned format;
unsigned stride;
bool online;
bool key;
long double grab_ts;
long double encode_begin_ts;
long double encode_end_ts;
long double last_client_ts;
uint8_t data[US_MEMSINK_MAX_DATA];
} us_memsink_shared_s;
omx_encoder_s *omx_encoder_init(void);
void omx_encoder_destroy(omx_encoder_s *omx);
INLINE us_memsink_shared_s *us_memsink_shared_map(int fd) {
us_memsink_shared_s *mem = mmap(
NULL,
sizeof(us_memsink_shared_s),
PROT_READ | PROT_WRITE,
MAP_SHARED,
fd,
0
);
if (mem == MAP_FAILED) {
return NULL;
}
assert(mem != NULL);
return mem;
}
int omx_encoder_prepare(omx_encoder_s *omx, const frame_s *frame, unsigned quality);
int omx_encoder_compress(omx_encoder_s *omx, const frame_s *src, frame_s *dest);
INLINE int us_memsink_shared_unmap(us_memsink_shared_s *mem) {
assert(mem != NULL);
return munmap(mem, sizeof(us_memsink_shared_s));
}

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

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

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

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

View File

@@ -1,8 +1,8 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -72,26 +72,25 @@ extern char **environ;
#ifdef HAS_PDEATHSIG
INLINE int process_track_parent_death(void) {
pid_t parent = getppid();
INLINE int us_process_track_parent_death(void) {
const pid_t parent = getppid();
int signum = SIGTERM;
# if defined(__linux__)
int retval = prctl(PR_SET_PDEATHSIG, signum);
const int retval = prctl(PR_SET_PDEATHSIG, signum);
# elif defined(__FreeBSD__)
int retval = procctl(P_PID, 0, PROC_PDEATHSIG_CTL, &signum);
const int retval = procctl(P_PID, 0, PROC_PDEATHSIG_CTL, &signum);
# else
# error WTF?
# endif
if (retval < 0) {
LOG_PERROR("Can't set to receive SIGTERM on parent process death");
US_LOG_PERROR("Can't set to receive SIGTERM on parent process death");
return -1;
}
if (kill(parent, 0) < 0) {
LOG_PERROR("The parent process %d is already dead", parent);
US_LOG_PERROR("The parent process %d is already dead", parent);
return -1;
}
return 0;
}
#endif
@@ -99,21 +98,21 @@ INLINE int process_track_parent_death(void) {
#ifdef WITH_SETPROCTITLE
# pragma GCC diagnostic ignored "-Wunused-parameter"
# pragma GCC diagnostic push
INLINE void process_set_name_prefix(int argc, char *argv[], const char *prefix) {
INLINE void us_process_set_name_prefix(int argc, char *argv[], const char *prefix) {
# pragma GCC diagnostic pop
char *cmdline = NULL;
size_t allocated = 2048;
size_t used = 0;
A_REALLOC(cmdline, allocated);
US_REALLOC(cmdline, allocated);
cmdline[0] = '\0';
for (int index = 0; index < argc; ++index) {
size_t arg_len = strlen(argv[index]);
if (used + arg_len + 16 >= allocated) {
allocated += arg_len + 2048;
A_REALLOC(cmdline, allocated); // cppcheck-suppress memleakOnRealloc // False-positive (ok with assert)
US_REALLOC(cmdline, allocated); // cppcheck-suppress memleakOnRealloc // False-positive (ok with assert)
}
strcat(cmdline, " ");
@@ -130,10 +129,16 @@ INLINE void process_set_name_prefix(int argc, char *argv[], const char *prefix)
}
#endif
INLINE void process_notify_parent(void) {
pid_t parent = getppid();
INLINE void us_process_notify_parent(void) {
const pid_t parent = getppid();
if (kill(parent, SIGUSR2) < 0) {
LOG_PERROR("Can't send SIGUSR2 to the parent process %d", parent);
US_LOG_PERROR("Can't send SIGUSR2 to the parent process %d", parent);
}
}
INLINE void us_process_suicide(void) {
const pid_t pid = getpid();
if (kill(pid, SIGTERM) < 0) {
US_LOG_PERROR("Can't send SIGTERM to own pid %d", pid);
}
}

View File

@@ -1,8 +1,8 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -41,37 +41,38 @@
#ifdef PTHREAD_MAX_NAMELEN_NP
# define MAX_THREAD_NAME ((size_t)(PTHREAD_MAX_NAMELEN_NP))
# define US_MAX_THREAD_NAME ((size_t)(PTHREAD_MAX_NAMELEN_NP))
#else
# define MAX_THREAD_NAME ((size_t)16)
# define US_MAX_THREAD_NAME ((size_t)16)
#endif
#define A_THREAD_CREATE(_tid, _func, _arg) assert(!pthread_create(_tid, NULL, _func, _arg))
#define A_THREAD_JOIN(_tid) assert(!pthread_join(_tid, NULL))
#define US_THREAD_CREATE(x_tid, x_func, x_arg) assert(!pthread_create(&(x_tid), NULL, (x_func), (x_arg)))
#define US_THREAD_JOIN(x_tid) assert(!pthread_join((x_tid), NULL))
#ifdef WITH_PTHREAD_NP
# define A_THREAD_RENAME(_fmt, ...) { \
char _new_tname_buf[MAX_THREAD_NAME] = {0}; \
assert(snprintf(_new_tname_buf, MAX_THREAD_NAME, _fmt, ##__VA_ARGS__) > 0); \
thread_set_name(_new_tname_buf); \
# define US_THREAD_RENAME(x_fmt, ...) { \
char m_new_tname_buf[US_MAX_THREAD_NAME] = {0}; \
assert(snprintf(m_new_tname_buf, US_MAX_THREAD_NAME, (x_fmt), ##__VA_ARGS__) > 0); \
us_thread_set_name(m_new_tname_buf); \
}
#else
# define A_THREAD_RENAME(_fmt, ...)
# define US_THREAD_RENAME(_fmt, ...)
#endif
#define A_MUTEX_INIT(_mutex) assert(!pthread_mutex_init(_mutex, NULL))
#define A_MUTEX_DESTROY(_mutex) assert(!pthread_mutex_destroy(_mutex))
#define A_MUTEX_LOCK(_mutex) assert(!pthread_mutex_lock(_mutex))
#define A_MUTEX_UNLOCK(_mutex) assert(!pthread_mutex_unlock(_mutex))
#define US_MUTEX_INIT(x_mutex) assert(!pthread_mutex_init(&(x_mutex), NULL))
#define US_MUTEX_DESTROY(x_mutex) assert(!pthread_mutex_destroy(&(x_mutex)))
#define US_MUTEX_LOCK(x_mutex) assert(!pthread_mutex_lock(&(x_mutex)))
#define US_MUTEX_UNLOCK(x_mutex) assert(!pthread_mutex_unlock(&(x_mutex)))
#define A_COND_INIT(_cond) assert(!pthread_cond_init(_cond, NULL))
#define A_COND_DESTROY(_cond) assert(!pthread_cond_destroy(_cond))
#define A_COND_SIGNAL(...) assert(!pthread_cond_signal(__VA_ARGS__))
#define A_COND_WAIT_TRUE(_var, _cond, _mutex) { while(!_var) assert(!pthread_cond_wait(_cond, _mutex)); }
#define US_COND_INIT(x_cond) assert(!pthread_cond_init(&(x_cond), NULL))
#define US_COND_DESTROY(x_cond) assert(!pthread_cond_destroy(&(x_cond)))
#define US_COND_SIGNAL(x_cond) assert(!pthread_cond_signal(&(x_cond)))
#define US_COND_BROADCAST(x_cond) assert(!pthread_cond_broadcast(&(x_cond)))
#define US_COND_WAIT_FOR(x_var, x_cond, x_mutex) { while(!(x_var)) assert(!pthread_cond_wait(&(x_cond), &(x_mutex))); }
#ifdef WITH_PTHREAD_NP
INLINE void thread_set_name(const char *name) {
INLINE void us_thread_set_name(const char *name) {
# if defined(__linux__)
pthread_setname_np(pthread_self(), name);
# elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
@@ -79,45 +80,45 @@ INLINE void thread_set_name(const char *name) {
# elif defined(__NetBSD__)
pthread_setname_np(pthread_self(), "%s", (void *)name);
# else
# error thread_set_name() not implemented, you can disable it using WITH_PTHREAD_NP=0
# error us_thread_set_name() not implemented, you can disable it using WITH_PTHREAD_NP=0
# endif
}
#endif
INLINE void thread_get_name(char *name) { // Always required for logging
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, MAX_THREAD_NAME);
retval = pthread_getname_np(pthread_self(), name, US_MAX_THREAD_NAME);
# elif \
(defined(__FreeBSD__) && defined(__FreeBSD_version) && __FreeBSD_version >= 1103500) \
|| (defined(__OpenBSD__) && defined(OpenBSD) && OpenBSD >= 201905) \
|| defined(__DragonFly__)
pthread_get_name_np(pthread_self(), name, MAX_THREAD_NAME);
pthread_get_name_np(pthread_self(), name, US_MAX_THREAD_NAME);
if (name[0] != '\0') {
retval = 0;
}
# else
# error thread_get_name() not implemented, you can disable it using WITH_PTHREAD_NP=0
# error us_thread_get_name() not implemented, you can disable it using WITH_PTHREAD_NP=0
# endif
if (retval < 0) {
#endif
#if defined(__linux__)
pid_t tid = syscall(SYS_gettid);
const pid_t tid = syscall(SYS_gettid);
#elif defined(__FreeBSD__)
pid_t tid = syscall(SYS_thr_self);
const pid_t tid = syscall(SYS_thr_self);
#elif defined(__OpenBSD__)
pid_t tid = syscall(SYS_getthrid);
const pid_t tid = syscall(SYS_getthrid);
#elif defined(__NetBSD__)
pid_t tid = syscall(SYS__lwp_self);
const pid_t tid = syscall(SYS__lwp_self);
#elif defined(__DragonFly__)
pid_t tid = syscall(SYS_lwp_gettid);
const pid_t tid = syscall(SYS_lwp_gettid);
#else
pid_t tid = 0; // Makes cppcheck happy
const pid_t tid = 0; // Makes cppcheck happy
# warning gettid() not implemented
#endif
assert(snprintf(name, MAX_THREAD_NAME, "tid=%d", tid) > 0);
assert(snprintf(name, US_MAX_THREAD_NAME, "tid=%d", tid) > 0);
#ifdef WITH_PTHREAD_NP
}

View File

@@ -1,8 +1,8 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -26,44 +26,74 @@
#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>
#define A_CALLOC(_dest, _nmemb) assert((_dest = calloc(_nmemb, sizeof(*(_dest)))))
#define A_REALLOC(_dest, _nmemb) assert((_dest = realloc(_dest, _nmemb * sizeof(*(_dest)))))
#define MEMSET_ZERO(_obj) memset(&(_obj), 0, sizeof(_obj))
#if defined(__GLIBC__) && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 32
# define HAS_SIGABBREV_NP
#else
# include <signal.h>
#endif
#define ARRAY_LEN(_array) (sizeof(_array) / sizeof(_array[0]))
#ifdef NDEBUG
# error WTF dude? Asserts are good things!
#endif
#if CHAR_BIT != 8
# error There are not 8 bits in a char!
#endif
#define RN "\r\n"
#define INLINE inline __attribute__((always_inline))
#define UNUSED __attribute__((unused))
#define US_CALLOC(x_dest, x_nmemb) assert(((x_dest) = calloc((x_nmemb), sizeof(*(x_dest)))) != NULL)
#define US_REALLOC(x_dest, x_nmemb) assert(((x_dest) = realloc((x_dest), (x_nmemb) * sizeof(*(x_dest)))) != NULL)
#define US_DELETE(x_dest, x_free) { if (x_dest) { x_free(x_dest); } }
#define US_MEMSET_ZERO(x_obj) memset(&(x_obj), 0, sizeof(x_obj))
INLINE char *bool_to_string(bool flag) {
#define US_ASPRINTF(x_dest, x_fmt, ...) assert(asprintf(&(x_dest), (x_fmt), ##__VA_ARGS__) >= 0)
INLINE char *us_strdup(const char *str) {
char *const new = strdup(str);
assert(new != NULL);
return new;
}
INLINE const char *us_bool_to_string(bool flag) {
return (flag ? "true" : "false");
}
INLINE size_t align_size(size_t size, size_t to) {
INLINE size_t us_align_size(size_t size, size_t to) {
return ((size + (to - 1)) & ~(to - 1));
}
INLINE unsigned min_u(unsigned a, unsigned b) {
INLINE unsigned us_min_u(unsigned a, unsigned b) {
return (a < b ? a : b);
}
INLINE unsigned max_u(unsigned a, unsigned b) {
INLINE unsigned us_max_u(unsigned a, unsigned b) {
return (a > b ? a : b);
}
INLINE long long floor_ms(long double now) {
INLINE long long us_floor_ms(long double now) {
return (long long)now - (now < (long long)now); // floor()
}
INLINE uint32_t triple_u32(uint32_t x) {
INLINE uint32_t us_triple_u32(uint32_t x) {
// https://nullprogram.com/blog/2018/07/31/
x ^= x >> 17;
x *= UINT32_C(0xED5AD4BB);
@@ -75,7 +105,7 @@ INLINE uint32_t triple_u32(uint32_t x) {
return x;
}
INLINE void get_now(clockid_t clk_id, time_t *sec, long *msec) {
INLINE void us_get_now(clockid_t clk_id, time_t *sec, long *msec) {
struct timespec ts;
assert(!clock_gettime(clk_id, &ts));
*sec = ts.tv_sec;
@@ -88,38 +118,103 @@ INLINE void get_now(clockid_t clk_id, time_t *sec, long *msec) {
}
#if defined(CLOCK_MONOTONIC_RAW)
# define X_CLOCK_MONOTONIC CLOCK_MONOTONIC_RAW
# define _X_CLOCK_MONOTONIC CLOCK_MONOTONIC_RAW
#elif defined(CLOCK_MONOTONIC_FAST)
# define X_CLOCK_MONOTONIC CLOCK_MONOTONIC_FAST
# define _X_CLOCK_MONOTONIC CLOCK_MONOTONIC_FAST
#else
# define X_CLOCK_MONOTONIC CLOCK_MONOTONIC
# define _X_CLOCK_MONOTONIC CLOCK_MONOTONIC
#endif
INLINE long double get_now_monotonic(void) {
INLINE long double us_get_now_monotonic(void) {
time_t sec;
long msec;
get_now(X_CLOCK_MONOTONIC, &sec, &msec);
us_get_now(_X_CLOCK_MONOTONIC, &sec, &msec);
return (long double)sec + ((long double)msec) / 1000;
}
INLINE uint64_t get_now_id(void) {
INLINE uint64_t us_get_now_monotonic_u64(void) {
struct timespec ts;
assert(!clock_gettime(X_CLOCK_MONOTONIC, &ts));
uint64_t now = (uint64_t)(ts.tv_nsec / 1000) + (uint64_t)ts.tv_sec * 1000000;
return (uint64_t)triple_u32(now) | ((uint64_t)triple_u32(now + 12345) << 32);
assert(!clock_gettime(_X_CLOCK_MONOTONIC, &ts));
return (uint64_t)(ts.tv_nsec / 1000) + (uint64_t)ts.tv_sec * 1000000;
}
#undef X_CLOCK_MONOTONIC
#undef _X_CLOCK_MONOTONIC
INLINE long double get_now_real(void) {
INLINE uint64_t us_get_now_id(void) {
const uint64_t now = us_get_now_monotonic_u64();
return (uint64_t)us_triple_u32(now) | ((uint64_t)us_triple_u32(now + 12345) << 32);
}
INLINE long double us_get_now_real(void) {
time_t sec;
long msec;
get_now(CLOCK_REALTIME, &sec, &msec);
us_get_now(CLOCK_REALTIME, &sec, &msec);
return (long double)sec + ((long double)msec) / 1000;
}
INLINE unsigned get_cores_available(void) {
INLINE unsigned us_get_cores_available(void) {
long cores_sysconf = sysconf(_SC_NPROCESSORS_ONLN);
cores_sysconf = (cores_sysconf < 0 ? 0 : cores_sysconf);
return max_u(min_u(cores_sysconf, 4), 1);
return us_max_u(us_min_u(cores_sysconf, 4), 1);
}
INLINE void us_ld_to_timespec(long double ld, struct timespec *ts) {
ts->tv_sec = (long)ld;
ts->tv_nsec = (ld - ts->tv_sec) * 1000000000L;
if (ts->tv_nsec > 999999999L) {
ts->tv_sec += 1;
ts->tv_nsec = 0;
}
}
INLINE long double us_timespec_to_ld(const struct timespec *ts) {
return ts->tv_sec + ((long double)ts->tv_nsec) / 1000000000;
}
INLINE int us_flock_timedwait_monotonic(int fd, long double timeout) {
const long double deadline_ts = us_get_now_monotonic() + timeout;
int retval = -1;
while (true) {
retval = flock(fd, LOCK_EX | LOCK_NB);
if (retval == 0 || errno != EWOULDBLOCK || us_get_now_monotonic() > deadline_ts) {
break;
}
if (usleep(1000) < 0) {
break;
}
}
return retval;
}
INLINE char *us_errno_to_string(int error) {
locale_t locale = newlocale(LC_MESSAGES_MASK, "C", NULL);
char *buf;
if (locale) {
buf = us_strdup(strerror_l(error, locale));
freelocale(locale);
} else {
buf = us_strdup("!!! newlocale() error !!!");
}
return buf;
}
INLINE char *us_signum_to_string(int signum) {
# ifdef HAS_SIGABBREV_NP
const char *const name = sigabbrev_np(signum);
# else
const char *const name = (
signum == SIGTERM ? "TERM" :
signum == SIGINT ? "INT" :
signum == SIGPIPE ? "PIPE" :
NULL
);
# endif
char *buf;
if (name != NULL) {
US_ASPRINTF(buf, "SIG%s", name);
} else {
US_ASPRINTF(buf, "SIG[%d]", signum);
}
return buf;
}

View File

@@ -1,8 +1,8 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -26,15 +26,15 @@
typedef struct {
struct jpeg_error_mgr mgr; // Default manager
jmp_buf jmp;
const frame_s *frame;
const us_frame_s *frame;
} _jpeg_error_manager_s;
static void _jpeg_error_handler(j_common_ptr jpeg);
int unjpeg(const frame_s *src, frame_s *dest, bool decode) {
assert(is_jpeg(src->format));
int us_unjpeg(const us_frame_s *src, us_frame_s *dest, bool decode) {
assert(us_is_jpeg(src->format));
volatile int retval = 0;
@@ -57,7 +57,7 @@ int unjpeg(const frame_s *src, frame_s *dest, bool decode) {
jpeg_start_decompress(&jpeg);
frame_copy_meta(src, dest);
us_frame_copy_meta(src, dest);
dest->format = V4L2_PIX_FMT_RGB24;
dest->width = jpeg.output_width;
dest->height = jpeg.output_height;
@@ -68,10 +68,10 @@ int unjpeg(const frame_s *src, frame_s *dest, bool decode) {
JSAMPARRAY scanlines;
scanlines = (*jpeg.mem->alloc_sarray)((j_common_ptr) &jpeg, JPOOL_IMAGE, dest->stride, 1);
frame_realloc_data(dest, ((dest->width * dest->height) << 1) * 2);
us_frame_realloc_data(dest, ((dest->width * dest->height) << 1) * 2);
while (jpeg.output_scanline < jpeg.output_height) {
jpeg_read_scanlines(&jpeg, scanlines, 1);
frame_append_data(dest, scanlines[0], dest->stride);
us_frame_append_data(dest, scanlines[0], dest->stride);
}
jpeg_finish_decompress(&jpeg);
@@ -87,6 +87,6 @@ static void _jpeg_error_handler(j_common_ptr jpeg) {
char msg[JMSG_LENGTH_MAX];
(*jpeg_error->mgr.format_message)(jpeg, msg);
LOG_ERROR("Can't decompress %s JPEG: %s", jpeg_error->frame->name, msg);
US_LOG_ERROR("Can't decompress JPEG: %s", msg);
longjmp(jpeg_error->jmp, -1);
}

View File

@@ -1,8 +1,8 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -37,4 +37,4 @@
#include "frame.h"
int unjpeg(const frame_s *src, frame_s *dest, bool decode);
int us_unjpeg(const us_frame_s *src, us_frame_s *dest, bool decode);

View File

@@ -1,8 +1,8 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -26,18 +26,15 @@
#include <sys/ioctl.h>
#include "../libs/tools.h"
#include "../libs/logging.h"
#ifndef CFG_XIOCTL_RETRIES
# define CFG_XIOCTL_RETRIES 4
#ifndef US_CFG_XIOCTL_RETRIES
# define US_CFG_XIOCTL_RETRIES 4
#endif
#define XIOCTL_RETRIES ((unsigned)(CFG_XIOCTL_RETRIES))
#define _XIOCTL_RETRIES ((unsigned)(US_CFG_XIOCTL_RETRIES))
INLINE int xioctl(int fd, int request, void *arg) {
int retries = XIOCTL_RETRIES;
INLINE int us_xioctl(int fd, int request, void *arg) {
int retries = _XIOCTL_RETRIES;
int retval = -1;
do {
@@ -51,10 +48,5 @@ INLINE int xioctl(int fd, int request, void *arg) {
|| errno == ETIMEDOUT
)
);
// cppcheck-suppress knownConditionTrueFalse
if (retval && retries <= 0) {
LOG_PERROR("ioctl(%d) retried %u times; giving up", request, XIOCTL_RETRIES);
}
return retval;
}

View File

@@ -1,8 +1,8 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -23,85 +23,83 @@
#include "blank.h"
static frame_s *_init_internal(void);
static frame_s *_init_external(const char *path);
static us_frame_s *_init_internal(void);
static us_frame_s *_init_external(const char *path);
frame_s *blank_frame_init(const char *path) {
frame_s *blank = NULL;
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) {
LOG_INFO("Using external blank placeholder: %s", path);
if (blank != NULL) {
US_LOG_INFO("Using external blank placeholder: %s", path);
} else {
blank = _init_internal();
LOG_INFO("Using internal blank placeholder");
US_LOG_INFO("Using internal blank placeholder");
}
return blank;
}
static frame_s *_init_internal(void) {
frame_s *blank = frame_init("blank_internal");
frame_set_data(blank, BLANK_JPEG_DATA, BLANK_JPEG_DATA_SIZE);
blank->width = BLANK_JPEG_WIDTH;
blank->height = BLANK_JPEG_HEIGHT;
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;
}
static frame_s *_init_external(const char *path) {
static us_frame_s *_init_external(const char *path) {
FILE *fp = NULL;
frame_s *blank = frame_init("blank_external");
us_frame_s *blank = us_frame_init();
blank->format = V4L2_PIX_FMT_JPEG;
if ((fp = fopen(path, "rb")) == NULL) {
LOG_PERROR("Can't open blank placeholder '%s'", path);
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) {
frame_realloc_data(blank, blank->used + CHUNK_SIZE * 2);
us_frame_realloc_data(blank, blank->used + CHUNK_SIZE * 2);
}
size_t readed = fread(blank->data + blank->used, 1, CHUNK_SIZE, fp);
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 {
LOG_PERROR("Can't read blank placeholder");
US_LOG_PERROR("Can't read blank placeholder");
goto error;
}
}
}
# undef CHUNK_SIZE
frame_s *decoded = frame_init("blank_external_decoded");
if (unjpeg(blank, decoded, false) < 0) {
frame_destroy(decoded);
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;
frame_destroy(decoded);
us_frame_destroy(decoded);
goto ok;
error:
frame_destroy(blank);
us_frame_destroy(blank);
blank = NULL;
ok:
if (fp) {
fclose(fp);
}
US_DELETE(fp, fclose);
return blank;
}

View File

@@ -1,8 +1,8 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -35,4 +35,4 @@
#include "data/blank_jpeg.h"
frame_s *blank_frame_init(const char *path);
us_frame_s *us_blank_frame_init(const char *path);

View File

@@ -1,8 +1,8 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -22,11 +22,11 @@
#include "blank_jpeg.h"
const unsigned BLANK_JPEG_WIDTH = 640;
const unsigned BLANK_JPEG_HEIGHT = 480;
const unsigned US_BLANK_JPEG_WIDTH = 640;
const unsigned US_BLANK_JPEG_HEIGHT = 480;
const size_t BLANK_JPEG_DATA_SIZE = 13845;
const uint8_t BLANK_JPEG_DATA[] = {
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,

View File

@@ -1,8 +1,8 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -27,8 +27,8 @@
#include <sys/types.h>
extern const unsigned BLANK_JPEG_WIDTH;
extern const unsigned BLANK_JPEG_HEIGHT;
extern const unsigned US_BLANK_JPEG_WIDTH;
extern const unsigned US_BLANK_JPEG_HEIGHT;
extern const size_t BLANK_JPEG_DATA_SIZE;
extern const uint8_t BLANK_JPEG_DATA[];
extern const size_t US_BLANK_JPEG_DATA_SIZE;
extern const uint8_t US_BLANK_JPEG_DATA[];

View File

@@ -12,30 +12,30 @@
<hr>
<ul>
<li>
<a href="/state"><b>/state</b></a><br>
<a href="state"><b>/state</b></a><br>
Get JSON structure with the state of the server.
</li>
<br>
<li>
<a href="/snapshot"><b>/snapshot</b></a><br>
<a href="snapshot"><b>/snapshot</b></a><br>
Get a current actual image from the server.
</li>
<br>
<li>
<a href="/stream"><b>/stream</b></a><br>
<a href="stream"><b>/stream</b></a><br>
Get a live stream. Query params:<br>
<br>
<ul>
<li>
<b>key=abc123</b><br>
The user-defined key, which is part of cookie <i>stream_client</i>, which allows<br>
the stream client to determine its identifier and view statistics using <a href="/state">/state</a>.
the stream client to determine its identifier and view statistics using <a href="state">/state</a>.
</li>
<br>
<li>
<b>extra_headers=1</b><br>
Add <i>X-UStreamer-*</i> headers to the <a href="/stream">/stream</a> handle
(like with the <a href="/snapshot">/snapshot</a>).
Add <i>X-UStreamer-*</i> headers to the <a href="stream">/stream</a> handle
(like with the <a href="snapshot">/snapshot</a>).
</li>
<br>
<li>
@@ -62,9 +62,9 @@
The mjpg-streamer compatibility layer:<br>
<br>
<ul>
<li><a href="/?action=snapshot">/?action=snapshot</a> as alias to the <a href="/snapshot">/snapshot</a>.</li>
<li><a href="?action=snapshot">/?action=snapshot</a> as alias to the <a href="snapshot">/snapshot</a>.</li>
<br>
<li><a href="/?action=stream">/?action=stream</a> as alias to the <a href="/stream">/stream</a>.</li>
<li><a href="?action=stream">/?action=stream</a> as alias to the <a href="stream">/stream</a>.</li>
</ul>
</li>
</ul>

View File

@@ -1,8 +1,8 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -22,7 +22,7 @@
#include "index_html.h"
const char *const HTML_INDEX_PAGE = " \
const char *const US_HTML_INDEX_PAGE = " \
<!DOCTYPE html> \
\
<html> \
@@ -33,34 +33,34 @@ const char *const HTML_INDEX_PAGE = " \
</head> \
\
<body> \
<h3>&micro;Streamer v" VERSION "</h3> \
<h3>&micro;Streamer v" US_VERSION "</h3> \
<hr> \
<ul> \
<li> \
<a href=\"/state\"><b>/state</b></a><br> \
<a href=\"state\"><b>/state</b></a><br> \
Get JSON structure with the state of the server. \
</li> \
<br> \
<li> \
<a href=\"/snapshot\"><b>/snapshot</b></a><br> \
<a href=\"snapshot\"><b>/snapshot</b></a><br> \
Get a current actual image from the server. \
</li> \
<br> \
<li> \
<a href=\"/stream\"><b>/stream</b></a><br> \
<a href=\"stream\"><b>/stream</b></a><br> \
Get a live stream. Query params:<br> \
<br> \
<ul> \
<li> \
<b>key=abc123</b><br> \
The user-defined key, which is part of cookie <i>stream_client</i>, which allows<br> \
the stream client to determine its identifier and view statistics using <a href=\"/state\">/state</a>. \
the stream client to determine its identifier and view statistics using <a href=\"state\">/state</a>. \
</li> \
<br> \
<li> \
<b>extra_headers=1</b><br> \
Add <i>X-UStreamer-*</i> headers to the <a href=\"/stream\">/stream</a> handle \
(like with the <a href=\"/snapshot\">/snapshot</a>). \
Add <i>X-UStreamer-*</i> headers to the <a href=\"stream\">/stream</a> handle \
(like with the <a href=\"snapshot\">/snapshot</a>). \
</li> \
<br> \
<li> \
@@ -87,9 +87,9 @@ const char *const HTML_INDEX_PAGE = " \
The mjpg-streamer compatibility layer:<br> \
<br> \
<ul> \
<li><a href=\"/?action=snapshot\">/?action=snapshot</a> as alias to the <a href=\"/snapshot\">/snapshot</a>.</li> \
<li><a href=\"?action=snapshot\">/?action=snapshot</a> as alias to the <a href=\"snapshot\">/snapshot</a>.</li> \
<br> \
<li><a href=\"/?action=stream\">/?action=stream</a> as alias to the <a href=\"/stream\">/stream</a>.</li> \
<li><a href=\"?action=stream\">/?action=stream</a> as alias to the <a href=\"stream\">/stream</a>.</li> \
</ul> \
</li> \
</ul> \

View File

@@ -1,8 +1,8 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -24,7 +24,7 @@
#include <sys/types.h>
#include "../../libs/config.h"
#include "../../libs/const.h"
extern const char *const HTML_INDEX_PAGE;
extern const char *const US_HTML_INDEX_PAGE;

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -40,91 +40,82 @@
#include <pthread.h>
#include <linux/videodev2.h>
#include <linux/v4l2-controls.h>
#ifdef WITH_OMX
# include <interface/vcsm/user-vcsm.h>
#endif
#include "../libs/tools.h"
#include "../libs/array.h"
#include "../libs/logging.h"
#include "../libs/threading.h"
#include "../libs/frame.h"
#include "xioctl.h"
#include "../libs/xioctl.h"
#define VIDEO_MIN_WIDTH ((unsigned)160)
#define VIDEO_MAX_WIDTH ((unsigned)10240)
#define US_VIDEO_MIN_WIDTH ((unsigned)160)
#define US_VIDEO_MAX_WIDTH ((unsigned)10240)
#define VIDEO_MIN_HEIGHT ((unsigned)120)
#define VIDEO_MAX_HEIGHT ((unsigned)4320)
#define US_VIDEO_MIN_HEIGHT ((unsigned)120)
#define US_VIDEO_MAX_HEIGHT ((unsigned)4320)
#define VIDEO_MAX_FPS ((unsigned)120)
#define US_VIDEO_MAX_FPS ((unsigned)120)
#define STANDARD_UNKNOWN V4L2_STD_UNKNOWN
#define STANDARDS_STR "PAL, NTSC, SECAM"
#define US_STANDARD_UNKNOWN V4L2_STD_UNKNOWN
#define US_STANDARDS_STR "PAL, NTSC, SECAM"
#define FORMAT_UNKNOWN -1
#define FORMATS_STR "YUYV, UYVY, RGB565, RGB24, JPEG"
#define US_FORMAT_UNKNOWN -1
#define US_FORMATS_STR "YUYV, UYVY, RGB565, RGB24, MJPEG, JPEG"
#define IO_METHOD_UNKNOWN -1
#define IO_METHODS_STR "MMAP, USERPTR"
#define US_IO_METHOD_UNKNOWN -1
#define US_IO_METHODS_STR "MMAP, USERPTR"
typedef struct {
frame_s raw;
struct v4l2_buffer buf_info;
# ifdef WITH_OMX
int dma_fd;
int vcsm_handle;
# endif
pthread_mutex_t grabbed_mutex;
bool grabbed;
} hw_buffer_s;
us_frame_s raw;
struct v4l2_buffer buf;
int dma_fd;
bool grabbed;
} 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;
hw_buffer_s *hw_bufs;
bool capturing;
bool persistent_timeout_reported;
} device_runtime_s;
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;
} us_device_runtime_s;
typedef enum {
CTL_MODE_NONE = 0,
CTL_MODE_VALUE,
CTL_MODE_AUTO,
CTL_MODE_DEFAULT,
} control_mode_e;
} us_control_mode_e;
typedef struct {
control_mode_e mode;
int value;
} control_s;
us_control_mode_e mode;
int value;
} us_control_s;
typedef struct {
control_s brightness;
control_s contrast;
control_s saturation;
control_s hue;
control_s gamma;
control_s sharpness;
control_s backlight_compensation;
control_s white_balance;
control_s gain;
control_s color_effect;
control_s flip_vertical;
control_s flip_horizontal;
} controls_s;
us_control_s brightness;
us_control_s contrast;
us_control_s saturation;
us_control_s hue;
us_control_s gamma;
us_control_s sharpness;
us_control_s backlight_compensation;
us_control_s white_balance;
us_control_s gain;
us_control_s color_effect;
us_control_s rotate;
us_control_s flip_vertical;
us_control_s flip_horizontal;
} us_controls_s;
typedef struct {
char *path;
@@ -142,27 +133,25 @@ typedef struct {
bool persistent;
unsigned timeout;
controls_s ctl;
us_controls_s ctl;
device_runtime_s *run;
} device_s;
us_device_runtime_s *run;
} us_device_s;
device_s *device_init(void);
void device_destroy(device_s *dev);
us_device_s *us_device_init(void);
void us_device_destroy(us_device_s *dev);
int device_parse_format(const char *str);
v4l2_std_id device_parse_standard(const char *str);
int device_parse_io_method(const char *str);
int us_device_parse_format(const char *str);
v4l2_std_id us_device_parse_standard(const char *str);
int us_device_parse_io_method(const char *str);
int device_open(device_s *dev);
void device_close(device_s *dev);
int us_device_open(us_device_s *dev);
void us_device_close(us_device_s *dev);
#ifdef WITH_OMX
int device_export_to_vcsm(device_s *dev);
#endif
int device_switch_capturing(device_s *dev, bool enable);
int device_select(device_s *dev, bool *has_read, bool *has_write, bool *has_error);
int device_grab_buffer(device_s *dev, hw_buffer_s **hw);
int device_release_buffer(device_s *dev, hw_buffer_s *hw);
int device_consume_event(device_s *dev);
int us_device_export_to_dma(us_device_s *dev);
int us_device_switch_capturing(us_device_s *dev, bool enable);
int us_device_select(us_device_s *dev, bool *has_read, bool *has_write, bool *has_error);
int us_device_grab_buffer(us_device_s *dev, us_hw_buffer_s **hw);
int us_device_release_buffer(us_device_s *dev, us_hw_buffer_s *hw);
int us_device_consume_event(us_device_s *dev);

View File

@@ -1,8 +1,8 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -25,178 +25,144 @@
static const struct {
const char *name;
const encoder_type_e type;
const us_encoder_type_e type; // cppcheck-suppress unusedStructMember
} _ENCODER_TYPES[] = {
{"CPU", ENCODER_TYPE_CPU},
{"HW", ENCODER_TYPE_HW},
# ifdef WITH_OMX
{"OMX", ENCODER_TYPE_OMX},
# endif
{"NOOP", ENCODER_TYPE_NOOP},
{"CPU", US_ENCODER_TYPE_CPU},
{"HW", US_ENCODER_TYPE_HW},
{"M2M-VIDEO", US_ENCODER_TYPE_M2M_VIDEO},
{"M2M-IMAGE", US_ENCODER_TYPE_M2M_IMAGE},
{"M2M-MJPEG", US_ENCODER_TYPE_M2M_VIDEO},
{"M2M-JPEG", US_ENCODER_TYPE_M2M_IMAGE},
{"OMX", US_ENCODER_TYPE_M2M_IMAGE},
{"NOOP", US_ENCODER_TYPE_NOOP},
};
static void *_worker_job_init(worker_s *wr, void *v_enc);
static void *_worker_job_init(void *v_enc);
static void _worker_job_destroy(void *v_job);
static bool _worker_run_job(worker_s *wr);
static bool _worker_run_job(us_worker_s *wr);
#define ER(_next) enc->run->_next
#define _ER(x_next) enc->run->x_next
encoder_s *encoder_init(void) {
encoder_runtime_s *run;
A_CALLOC(run, 1);
run->type = ENCODER_TYPE_CPU;
us_encoder_s *us_encoder_init(void) {
us_encoder_runtime_s *run;
US_CALLOC(run, 1);
run->type = US_ENCODER_TYPE_CPU;
run->quality = 80;
A_MUTEX_INIT(&run->mutex);
US_MUTEX_INIT(run->mutex);
encoder_s *enc;
A_CALLOC(enc, 1);
us_encoder_s *enc;
US_CALLOC(enc, 1);
enc->type = run->type;
enc->n_workers = get_cores_available();
enc->n_workers = us_get_cores_available();
enc->run = run;
return enc;
}
void encoder_destroy(encoder_s *enc) {
# ifdef WITH_OMX
if (ER(omxs)) {
for (unsigned index = 0; index < ER(n_omxs); ++index) {
if (ER(omxs[index])) {
omx_encoder_destroy(ER(omxs[index]));
}
void us_encoder_destroy(us_encoder_s *enc) {
if (_ER(m2ms) != NULL) {
for (unsigned index = 0; index < _ER(n_m2ms); ++index) {
US_DELETE(_ER(m2ms[index]), us_m2m_encoder_destroy)
}
free(ER(omxs));
free(_ER(m2ms));
}
# endif
A_MUTEX_DESTROY(&ER(mutex));
US_MUTEX_DESTROY(_ER(mutex));
free(enc->run);
free(enc);
}
encoder_type_e encoder_parse_type(const char *str) {
for (unsigned index = 0; index < ARRAY_LEN(_ENCODER_TYPES); ++index) {
if (!strcasecmp(str, _ENCODER_TYPES[index].name)) {
return _ENCODER_TYPES[index].type;
us_encoder_type_e us_encoder_parse_type(const char *str) {
US_ARRAY_ITERATE(_ENCODER_TYPES, 0, item, {
if (!strcasecmp(item->name, str)) {
return item->type;
}
}
return ENCODER_TYPE_UNKNOWN;
});
return US_ENCODER_TYPE_UNKNOWN;
}
const char *encoder_type_to_string(encoder_type_e type) {
for (unsigned index = 0; index < ARRAY_LEN(_ENCODER_TYPES); ++index) {
if (_ENCODER_TYPES[index].type == type) {
return _ENCODER_TYPES[index].name;
const char *us_encoder_type_to_string(us_encoder_type_e type) {
US_ARRAY_ITERATE(_ENCODER_TYPES, 0, item, {
if (item->type == type) {
return item->name;
}
}
});
return _ENCODER_TYPES[0].name;
}
workers_pool_s *encoder_workers_pool_init(encoder_s *enc, device_s *dev) {
# define DR(_next) dev->run->_next
us_workers_pool_s *us_encoder_workers_pool_init(us_encoder_s *enc, us_device_s *dev) {
# define DR(x_next) dev->run->x_next
encoder_type_e type = (ER(cpu_forced) ? ENCODER_TYPE_CPU : enc->type);
us_encoder_type_e type = (_ER(cpu_forced) ? US_ENCODER_TYPE_CPU : enc->type);
unsigned quality = dev->jpeg_quality;
unsigned n_workers = min_u(enc->n_workers, DR(n_bufs));
unsigned n_workers = us_min_u(enc->n_workers, DR(n_bufs));
bool cpu_forced = false;
if (is_jpeg(DR(format)) && type != ENCODER_TYPE_HW) {
LOG_INFO("Switching to HW encoder: the input is (M)JPEG ...");
type = ENCODER_TYPE_HW;
if (us_is_jpeg(DR(format)) && type != US_ENCODER_TYPE_HW) {
US_LOG_INFO("Switching to HW encoder: the input is (M)JPEG ...");
type = US_ENCODER_TYPE_HW;
}
if (type == ENCODER_TYPE_HW) {
if (!is_jpeg(DR(format))) {
LOG_INFO("Switching to CPU encoder: the input format is not (M)JPEG ...");
if (type == US_ENCODER_TYPE_HW) {
if (!us_is_jpeg(DR(format))) {
US_LOG_INFO("Switching to CPU encoder: the input format is not (M)JPEG ...");
goto use_cpu;
}
quality = DR(jpeg_quality);
n_workers = 1;
}
# ifdef WITH_OMX
else if (type == ENCODER_TYPE_OMX) {
if (align_size(DR(width), 32) != DR(width)) {
LOG_INFO("Switching to CPU encoder: OMX can't handle width=%u ...", DR(width));
goto use_cpu;
} else if (type == US_ENCODER_TYPE_M2M_VIDEO || type == US_ENCODER_TYPE_M2M_IMAGE) {
US_LOG_DEBUG("Preparing M2M-%s encoder ...", (type == US_ENCODER_TYPE_M2M_VIDEO ? "VIDEO" : "IMAGE"));
if (_ER(m2ms) == NULL) {
US_CALLOC(_ER(m2ms), n_workers);
}
LOG_DEBUG("Preparing OMX encoder ...");
if (n_workers > OMX_MAX_ENCODERS) {
LOG_INFO("OMX encoder sets limit for worker threads: %u", OMX_MAX_ENCODERS);
n_workers = OMX_MAX_ENCODERS;
}
if (ER(omxs) == NULL) {
A_CALLOC(ER(omxs), OMX_MAX_ENCODERS);
}
// Начинаем с нуля и доинициализируем на следующих заходах при необходимости
for (; ER(n_omxs) < n_workers; ++ER(n_omxs)) {
if ((ER(omxs[ER(n_omxs)]) = omx_encoder_init()) == NULL) {
LOG_ERROR("Can't initialize OMX encoder, falling back to CPU");
goto force_cpu;
for (; _ER(n_m2ms) < n_workers; ++_ER(n_m2ms)) {
// Начинаем с нуля и доинициализируем на следующих заходах при необходимости
char name[32];
snprintf(name, 32, "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 {
_ER(m2ms[_ER(n_m2ms)]) = us_m2m_jpeg_encoder_init(name, enc->m2m_path, quality);
}
}
frame_s frame;
MEMSET_ZERO(frame);
frame.width = DR(width);
frame.height = DR(height);
frame.format = DR(format);
frame.stride = DR(stride);
for (unsigned index = 0; index < ER(n_omxs); ++index) {
int omx_error = omx_encoder_prepare(ER(omxs[index]), &frame, quality);
if (omx_error == -2) {
goto use_cpu;
} else if (omx_error < 0) {
goto force_cpu;
}
}
}
# endif
else if (type == ENCODER_TYPE_NOOP) {
} else if (type == US_ENCODER_TYPE_NOOP) {
n_workers = 1;
quality = 0;
}
goto ok;
# ifdef WITH_OMX
force_cpu:
LOG_ERROR("Forced CPU encoder permanently");
cpu_forced = true;
# endif
use_cpu:
type = ENCODER_TYPE_CPU;
type = US_ENCODER_TYPE_CPU;
quality = dev->jpeg_quality;
ok:
if (type == ENCODER_TYPE_NOOP) {
LOG_INFO("Using JPEG NOOP encoder");
if (type == US_ENCODER_TYPE_NOOP) {
US_LOG_INFO("Using JPEG NOOP encoder");
} else if (quality == 0) {
LOG_INFO("Using JPEG quality: encoder default");
US_LOG_INFO("Using JPEG quality: encoder default");
} else {
LOG_INFO("Using JPEG quality: %u%%", quality);
US_LOG_INFO("Using JPEG quality: %u%%", quality);
}
A_MUTEX_LOCK(&ER(mutex));
ER(type) = type;
ER(quality) = quality;
US_MUTEX_LOCK(_ER(mutex));
_ER(type) = type;
_ER(quality) = quality;
if (cpu_forced) {
ER(cpu_forced) = true;
_ER(cpu_forced) = true;
}
A_MUTEX_UNLOCK(&ER(mutex));
US_MUTEX_UNLOCK(_ER(mutex));
long double desired_interval = 0;
if (dev->desired_fps > 0 && (dev->desired_fps < dev->run->hw_fps || dev->run->hw_fps == 0)) {
desired_interval = (long double)1 / dev->desired_fps;
}
const long double desired_interval = (
dev->desired_fps > 0 && (dev->desired_fps < dev->run->hw_fps || dev->run->hw_fps == 0)
? (long double)1 / dev->desired_fps
: 0
);
return workers_pool_init(
return us_workers_pool_init(
"JPEG", "jw", n_workers, desired_interval,
_worker_job_init, (void *)enc,
_worker_job_destroy,
@@ -205,91 +171,73 @@ workers_pool_s *encoder_workers_pool_init(encoder_s *enc, device_s *dev) {
# undef DR
}
void encoder_get_runtime_params(encoder_s *enc, encoder_type_e *type, unsigned *quality) {
A_MUTEX_LOCK(&ER(mutex));
*type = ER(type);
*quality = ER(quality);
A_MUTEX_UNLOCK(&ER(mutex));
void us_encoder_get_runtime_params(us_encoder_s *enc, us_encoder_type_e *type, unsigned *quality) {
US_MUTEX_LOCK(_ER(mutex));
*type = _ER(type);
*quality = _ER(quality);
US_MUTEX_UNLOCK(_ER(mutex));
}
static void *_worker_job_init(worker_s *wr, void *v_enc) {
encoder_job_s *job;
A_CALLOC(job, 1);
job->enc = (encoder_s *)v_enc;
const size_t dest_role_len = strlen(wr->name) + 16;
A_CALLOC(job->dest_role, dest_role_len);
snprintf(job->dest_role, dest_role_len, "%s_dest", wr->name);
job->dest = frame_init(job->dest_role);
static void *_worker_job_init(void *v_enc) {
us_encoder_job_s *job;
US_CALLOC(job, 1);
job->enc = (us_encoder_s *)v_enc;
job->dest = us_frame_init();
return (void *)job;
}
static void _worker_job_destroy(void *v_job) {
encoder_job_s *job = (encoder_job_s *)v_job;
frame_destroy(job->dest);
free(job->dest_role);
us_encoder_job_s *job = (us_encoder_job_s *)v_job;
us_frame_destroy(job->dest);
free(job);
}
#undef ER
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;
us_frame_s *dest = job->dest;
static bool _worker_run_job(worker_s *wr) {
encoder_job_s *job = (encoder_job_s *)wr->job;
frame_s *src = &job->hw->raw;
frame_s *dest = job->dest;
assert(_ER(type) != US_ENCODER_TYPE_UNKNOWN);
# define ER(_next) job->enc->run->_next
if (_ER(type) == US_ENCODER_TYPE_CPU) {
US_LOG_VERBOSE("Compressing JPEG using CPU: worker=%s, buffer=%u",
wr->name, job->hw->buf.index);
us_cpu_encoder_compress(src, dest, _ER(quality));
LOG_DEBUG("Worker %s compressing JPEG from buffer %u ...", wr->name, job->hw->buf_info.index);
} else if (_ER(type) == US_ENCODER_TYPE_HW) {
US_LOG_VERBOSE("Compressing JPEG using HW (just copying): worker=%s, buffer=%u",
wr->name, job->hw->buf.index);
us_hw_encoder_compress(src, dest);
assert(ER(type) != ENCODER_TYPE_UNKNOWN);
assert(src->used > 0);
frame_copy_meta(src, dest);
dest->format = V4L2_PIX_FMT_JPEG;
dest->stride = 0;
dest->encode_begin_ts = get_now_monotonic();
dest->used = 0;
if (ER(type) == ENCODER_TYPE_CPU) {
LOG_VERBOSE("Compressing buffer using CPU");
cpu_encoder_compress(src, dest, ER(quality));
} else if (ER(type) == ENCODER_TYPE_HW) {
LOG_VERBOSE("Compressing buffer using HW (just copying)");
hw_encoder_compress(src, dest);
}
# ifdef WITH_OMX
else if (ER(type) == ENCODER_TYPE_OMX) {
LOG_VERBOSE("Compressing buffer using OMX");
if (omx_encoder_compress(ER(omxs[wr->number]), src, dest) < 0) {
} else if (_ER(type) == US_ENCODER_TYPE_M2M_VIDEO || _ER(type) == US_ENCODER_TYPE_M2M_IMAGE) {
US_LOG_VERBOSE("Compressing JPEG using M2M-%s: worker=%s, buffer=%u",
(_ER(type) == US_ENCODER_TYPE_M2M_VIDEO ? "VIDEO" : "IMAGE"), wr->name, job->hw->buf.index);
if (us_m2m_encoder_compress(_ER(m2ms[wr->number]), src, dest, false) < 0) {
goto error;
}
}
# endif
else if (ER(type) == ENCODER_TYPE_NOOP) {
LOG_VERBOSE("Compressing buffer using NOOP (do nothing)");
} else if (_ER(type) == US_ENCODER_TYPE_NOOP) {
US_LOG_VERBOSE("Compressing JPEG using NOOP (do nothing): worker=%s, buffer=%u",
wr->name, job->hw->buf.index);
us_frame_encoding_begin(src, dest, V4L2_PIX_FMT_JPEG);
usleep(5000); // Просто чтобы работала логика desired_fps
dest->encode_end_ts = us_get_now_monotonic(); // us_frame_encoding_end()
}
dest->encode_end_ts = get_now_monotonic();
LOG_VERBOSE("Compressed new JPEG: size=%zu, time=%0.3Lf, worker=%s, buffer=%u",
US_LOG_VERBOSE("Compressed new JPEG: size=%zu, time=%0.3Lf, worker=%s, buffer=%u",
job->dest->used,
job->dest->encode_end_ts - job->dest->encode_begin_ts,
wr->name,
job->hw->buf_info.index);
job->hw->buf.index);
return true;
# ifdef WITH_OMX
error:
LOG_ERROR("Compression failed: worker=%s, buffer=%u", wr->name, job->hw->buf_info.index);
LOG_ERROR("Error while compressing buffer, falling back to CPU");
A_MUTEX_LOCK(&ER(mutex));
ER(cpu_forced) = true;
A_MUTEX_UNLOCK(&ER(mutex));
US_LOG_ERROR("Compression failed: worker=%s, buffer=%u", wr->name, job->hw->buf.index);
US_LOG_ERROR("Error while compressing buffer, falling back to CPU");
US_MUTEX_LOCK(_ER(mutex));
_ER(cpu_forced) = true;
US_MUTEX_UNLOCK(_ER(mutex));
return false;
# endif
# undef ER
}

View File

@@ -1,8 +1,8 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -31,73 +31,62 @@
#include <linux/videodev2.h>
#include "../libs/tools.h"
#include "../libs/array.h"
#include "../libs/threading.h"
#include "../libs/logging.h"
#include "../libs/frame.h"
#include "device.h"
#include "workers.h"
#include "m2m.h"
#include "encoders/cpu/encoder.h"
#include "encoders/hw/encoder.h"
#ifdef WITH_OMX
# include "encoders/omx/encoder.h"
# define ENCODER_TYPES_OMX_HINT ", OMX"
#else
# define ENCODER_TYPES_OMX_HINT ""
#endif
#define ENCODER_TYPES_STR \
"CPU, HW" \
ENCODER_TYPES_OMX_HINT \
", NOOP"
#define ENCODER_TYPES_STR "CPU, HW, M2M-VIDEO, M2M-IMAGE, NOOP"
typedef enum {
ENCODER_TYPE_UNKNOWN, // Only for encoder_parse_type() and main()
ENCODER_TYPE_CPU,
ENCODER_TYPE_HW,
# ifdef WITH_OMX
ENCODER_TYPE_OMX,
# endif
ENCODER_TYPE_NOOP,
} encoder_type_e;
US_ENCODER_TYPE_UNKNOWN, // Only for us_encoder_parse_type() and main()
US_ENCODER_TYPE_CPU,
US_ENCODER_TYPE_HW,
US_ENCODER_TYPE_M2M_VIDEO,
US_ENCODER_TYPE_M2M_IMAGE,
US_ENCODER_TYPE_NOOP,
} us_encoder_type_e;
typedef struct {
encoder_type_e type;
unsigned quality;
bool cpu_forced;
pthread_mutex_t mutex;
us_encoder_type_e type;
unsigned quality;
bool cpu_forced;
pthread_mutex_t mutex;
# ifdef WITH_OMX
unsigned n_omxs;
omx_encoder_s **omxs;
# endif
} encoder_runtime_s;
unsigned n_m2ms;
us_m2m_encoder_s **m2ms;
} us_encoder_runtime_s;
typedef struct {
encoder_type_e type;
unsigned n_workers;
us_encoder_type_e type;
unsigned n_workers;
char *m2m_path;
encoder_runtime_s *run;
} encoder_s;
us_encoder_runtime_s *run;
} us_encoder_s;
typedef struct {
encoder_s *enc;
hw_buffer_s *hw;
char *dest_role;
frame_s *dest;
} encoder_job_s;
us_encoder_s *enc;
us_hw_buffer_s *hw;
us_frame_s *dest;
} us_encoder_job_s;
encoder_s *encoder_init(void);
void encoder_destroy(encoder_s *enc);
us_encoder_s *us_encoder_init(void);
void us_encoder_destroy(us_encoder_s *enc);
encoder_type_e encoder_parse_type(const char *str);
const char *encoder_type_to_string(encoder_type_e type);
us_encoder_type_e us_encoder_parse_type(const char *str);
const char *us_encoder_type_to_string(us_encoder_type_e type);
workers_pool_s *encoder_workers_pool_init(encoder_s *enc, device_s *dev);
void encoder_get_runtime_params(encoder_s *enc, encoder_type_e *type, unsigned *quality);
us_workers_pool_s *us_encoder_workers_pool_init(us_encoder_s *enc, us_device_s *dev);
void us_encoder_get_runtime_params(us_encoder_s *enc, us_encoder_type_e *type, unsigned *quality);
int encoder_compress(encoder_s *enc, unsigned worker_number, frame_s *src, frame_s *dest);
int us_encoder_compress(us_encoder_s *enc, unsigned worker_number, us_frame_s *src, us_frame_s *dest);

View File

@@ -1,13 +1,13 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# This source file based on code of MJPG-Streamer. #
# #
# Copyright (C) 2005-2006 Laurent Pinchart & Michel Xhaard #
# Copyright (C) 2006 Gabriel A. Devenyi #
# Copyright (C) 2007 Tom Stöveken #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -31,25 +31,27 @@
typedef struct {
struct jpeg_destination_mgr mgr; // Default manager
JOCTET *buf; // Start of buffer
frame_s *frame;
us_frame_s *frame;
} _jpeg_dest_manager_s;
static void _jpeg_set_dest_frame(j_compress_ptr jpeg, frame_s *frame);
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 frame_s *frame);
static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg, const frame_s *frame);
static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, const frame_s *frame);
static void _jpeg_write_scanlines_rgb24(struct jpeg_compress_struct *jpeg, const frame_s *frame);
static void _jpeg_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_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_init_destination(j_compress_ptr jpeg);
static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg);
static void _jpeg_term_destination(j_compress_ptr jpeg);
void cpu_encoder_compress(const frame_s *src, frame_s *dest, unsigned quality) {
void us_cpu_encoder_compress(const us_frame_s *src, us_frame_s *dest, unsigned quality) {
// This function based on compress_image_to_jpeg() from mjpg-streamer
us_frame_encoding_begin(src, dest, V4L2_PIX_FMT_JPEG);
struct jpeg_compress_struct jpeg;
struct jpeg_error_mgr jpeg_error;
@@ -68,8 +70,8 @@ void cpu_encoder_compress(const frame_s *src, frame_s *dest, unsigned quality) {
jpeg_start_compress(&jpeg, TRUE);
# define WRITE_SCANLINES(_format, _func) \
case _format: { _func(&jpeg, src); break; }
# define WRITE_SCANLINES(x_format, x_func) \
case x_format: { x_func(&jpeg, src); break; }
switch (src->format) {
// https://www.fourcc.org/yuv.php
@@ -85,17 +87,17 @@ void cpu_encoder_compress(const frame_s *src, frame_s *dest, unsigned quality) {
jpeg_finish_compress(&jpeg);
jpeg_destroy_compress(&jpeg);
assert(dest->used > 0);
us_frame_encoding_end(dest);
}
static void _jpeg_set_dest_frame(j_compress_ptr jpeg, frame_s *frame) {
static void _jpeg_set_dest_frame(j_compress_ptr jpeg, us_frame_s *frame) {
if (jpeg->dest == NULL) {
assert((jpeg->dest = (struct jpeg_destination_mgr *)(*jpeg->mem->alloc_small)(
(j_common_ptr) jpeg, JPOOL_PERMANENT, sizeof(_jpeg_dest_manager_s)
)));
)) != NULL);
}
_jpeg_dest_manager_s *dest = (_jpeg_dest_manager_s *)jpeg->dest;
_jpeg_dest_manager_s *const dest = (_jpeg_dest_manager_s *)jpeg->dest;
dest->mgr.init_destination = _jpeg_init_destination;
dest->mgr.empty_output_buffer = _jpeg_empty_output_buffer;
dest->mgr.term_destination = _jpeg_term_destination;
@@ -109,11 +111,11 @@ static void _jpeg_set_dest_frame(j_compress_ptr jpeg, frame_s *frame) {
#define YUV_B(_y, _u, _) (((_y) + (454 * (_u))) >> 8)
#define NORM_COMPONENT(_x) (((_x) > 255) ? 255 : (((_x) < 0) ? 0 : (_x)))
static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg, const frame_s *frame) {
static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) {
uint8_t *line_buf;
A_CALLOC(line_buf, frame->width * 3);
US_CALLOC(line_buf, frame->width * 3);
const unsigned padding = frame_get_padding(frame);
const unsigned padding = us_frame_get_padding(frame);
const uint8_t *data = frame->data;
unsigned z = 0;
@@ -121,13 +123,13 @@ static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg, const
uint8_t *ptr = line_buf;
for (unsigned x = 0; x < frame->width; ++x) {
int y = (!z ? data[0] << 8 : data[2] << 8);
int u = data[1] - 128;
int v = data[3] - 128;
const int y = (!z ? data[0] << 8 : data[2] << 8);
const int u = data[1] - 128;
const int v = data[3] - 128;
int r = YUV_R(y, u, v);
int g = YUV_G(y, u, v);
int b = YUV_B(y, u, v);
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);
@@ -147,11 +149,11 @@ 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 frame_s *frame) {
static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) {
uint8_t *line_buf;
A_CALLOC(line_buf, frame->width * 3);
US_CALLOC(line_buf, frame->width * 3);
const unsigned padding = frame_get_padding(frame);
const unsigned padding = us_frame_get_padding(frame);
const uint8_t *data = frame->data;
unsigned z = 0;
@@ -159,13 +161,13 @@ static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg, const
uint8_t *ptr = line_buf;
for (unsigned x = 0; x < frame->width; ++x) {
int y = (!z ? data[1] << 8 : data[3] << 8);
int u = data[0] - 128;
int v = data[2] - 128;
const int y = (!z ? data[1] << 8 : data[3] << 8);
const int u = data[0] - 128;
const int v = data[2] - 128;
int r = YUV_R(y, u, v);
int g = YUV_G(y, u, v);
int b = YUV_B(y, u, v);
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);
@@ -190,18 +192,18 @@ static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg, const
#undef YUV_G
#undef YUV_R
static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, const frame_s *frame) {
static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) {
uint8_t *line_buf;
A_CALLOC(line_buf, frame->width * 3);
US_CALLOC(line_buf, frame->width * 3);
const unsigned padding = frame_get_padding(frame);
const unsigned padding = us_frame_get_padding(frame);
const uint8_t *data = frame->data;
while (jpeg->next_scanline < frame->height) {
uint8_t *ptr = line_buf;
for (unsigned x = 0; x < frame->width; ++x) {
unsigned int two_byte = (data[1] << 8) + data[0];
const unsigned int two_byte = (data[1] << 8) + data[0];
*(ptr++) = data[1] & 248; // Red
*(ptr++) = (uint8_t)((two_byte & 2016) >> 3); // Green
@@ -218,8 +220,8 @@ static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, cons
free(line_buf);
}
static void _jpeg_write_scanlines_rgb24(struct jpeg_compress_struct *jpeg, const frame_s *frame) {
const unsigned padding = frame_get_padding(frame);
static void _jpeg_write_scanlines_rgb24(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) {
const unsigned padding = us_frame_get_padding(frame);
uint8_t *data = frame->data;
while (jpeg->next_scanline < frame->height) {
@@ -233,12 +235,12 @@ static void _jpeg_write_scanlines_rgb24(struct jpeg_compress_struct *jpeg, const
#define JPEG_OUTPUT_BUFFER_SIZE ((size_t)4096)
static void _jpeg_init_destination(j_compress_ptr jpeg) {
_jpeg_dest_manager_s *dest = (_jpeg_dest_manager_s *)jpeg->dest;
_jpeg_dest_manager_s *const dest = (_jpeg_dest_manager_s *)jpeg->dest;
// Allocate the output buffer - it will be released when done with image
assert((dest->buf = (JOCTET *)(*jpeg->mem->alloc_small)(
(j_common_ptr) jpeg, JPOOL_IMAGE, JPEG_OUTPUT_BUFFER_SIZE * sizeof(JOCTET)
)));
)) != NULL);
dest->mgr.next_output_byte = dest->buf;
dest->mgr.free_in_buffer = JPEG_OUTPUT_BUFFER_SIZE;
@@ -247,9 +249,9 @@ static void _jpeg_init_destination(j_compress_ptr jpeg) {
static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg) {
// Called whenever local jpeg buffer fills up
_jpeg_dest_manager_s *dest = (_jpeg_dest_manager_s *)jpeg->dest;
_jpeg_dest_manager_s *const dest = (_jpeg_dest_manager_s *)jpeg->dest;
frame_append_data(dest->frame, dest->buf, JPEG_OUTPUT_BUFFER_SIZE);
us_frame_append_data(dest->frame, dest->buf, JPEG_OUTPUT_BUFFER_SIZE);
dest->mgr.next_output_byte = dest->buf;
dest->mgr.free_in_buffer = JPEG_OUTPUT_BUFFER_SIZE;
@@ -261,11 +263,11 @@ static void _jpeg_term_destination(j_compress_ptr jpeg) {
// Called by jpeg_finish_compress after all data has been written.
// Usually needs to flush buffer.
_jpeg_dest_manager_s *dest = (_jpeg_dest_manager_s *)jpeg->dest;
size_t final = JPEG_OUTPUT_BUFFER_SIZE - dest->mgr.free_in_buffer;
_jpeg_dest_manager_s *const dest = (_jpeg_dest_manager_s *)jpeg->dest;
const size_t final = JPEG_OUTPUT_BUFFER_SIZE - dest->mgr.free_in_buffer;
// Write any data remaining in the buffer.
frame_append_data(dest->frame, dest->buf, final);
us_frame_append_data(dest->frame, dest->buf, final);
}
#undef JPEG_OUTPUT_BUFFER_SIZE

View File

@@ -1,8 +1,8 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -35,4 +35,4 @@
#include "../../../libs/frame.h"
void cpu_encoder_compress(const frame_s *src, frame_s *dest, unsigned quality);
void us_cpu_encoder_compress(const us_frame_s *src, us_frame_s *dest, unsigned quality);

View File

@@ -1,13 +1,13 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# This source file based on code of MJPG-Streamer. #
# #
# Copyright (C) 2005-2006 Laurent Pinchart & Michel Xhaard #
# Copyright (C) 2006 Gabriel A. Devenyi #
# Copyright (C) 2007 Tom Stöveken #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -28,19 +28,21 @@
#include "encoder.h"
void _copy_plus_huffman(const frame_s *src, frame_s *dest);
void _copy_plus_huffman(const us_frame_s *src, us_frame_s *dest);
static bool _is_huffman(const uint8_t *data);
void hw_encoder_compress(const frame_s *src, frame_s *dest) {
assert(is_jpeg(src->format));
void us_hw_encoder_compress(const us_frame_s *src, us_frame_s *dest) {
assert(us_is_jpeg(src->format));
_copy_plus_huffman(src, dest);
}
void _copy_plus_huffman(const frame_s *src, frame_s *dest) {
void _copy_plus_huffman(const us_frame_s *src, us_frame_s *dest) {
us_frame_encoding_begin(src, dest, V4L2_PIX_FMT_JPEG);
if (!_is_huffman(src->data)) {
const uint8_t *src_ptr = src->data;
const uint8_t *src_end = src->data + src->used;
const uint8_t *const src_end = src->data + src->used;
while ((((src_ptr[0] << 8) | src_ptr[1]) != 0xFFC0) && (src_ptr < src_end)) {
src_ptr += 1;
@@ -52,12 +54,15 @@ void _copy_plus_huffman(const frame_s *src, frame_s *dest) {
const size_t paste = src_ptr - src->data;
frame_set_data(dest, src->data, paste);
frame_append_data(dest, HUFFMAN_TABLE, sizeof(HUFFMAN_TABLE));
frame_append_data(dest, src_ptr, src->used - paste);
us_frame_set_data(dest, src->data, paste);
us_frame_append_data(dest, US_HUFFMAN_TABLE, sizeof(US_HUFFMAN_TABLE));
us_frame_append_data(dest, src_ptr, src->used - paste);
} else {
frame_set_data(dest, src->data, src->used);
us_frame_set_data(dest, src->data, src->used);
}
us_frame_encoding_end(dest);
}
static bool _is_huffman(const uint8_t *data) {

View File

@@ -1,8 +1,8 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -34,4 +34,4 @@
#include "huffman.h"
void hw_encoder_compress(const frame_s *src, frame_s *dest);
void us_hw_encoder_compress(const us_frame_s *src, us_frame_s *dest);

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