Compare commits

...

276 Commits
v1.19 ... v3.8

Author SHA1 Message Date
Devaev Maxim
1b08857534 Bump version: 3.7 → 3.8 2021-01-21 12:03:35 +03:00
Devaev Maxim
eec19892fa fixed PKGBUILD 2021-01-21 12:03:01 +03:00
Devaev Maxim
40abe73391 Bump version: 3.6 → 3.7 2021-01-21 11:51:34 +03:00
Devaev Maxim
e4f1ef654f lint fix 2021-01-21 11:34:24 +03:00
Devaev Maxim
fa6afb96ce check usleep() retval 2021-01-21 11:22:04 +03:00
Devaev Maxim
c3f98b34f2 python builddeps 2021-01-21 09:27:29 +03:00
Devaev Maxim
b7b3e8e87d python: handle signals 2021-01-21 09:18:09 +03:00
Devaev Maxim
94383a2d54 moved python module 2021-01-21 07:19:10 +03:00
Devaev Maxim
184a4879eb gzip force 2021-01-21 05:23:22 +03:00
Devaev Maxim
bf48908c59 fixed another segfault 2021-01-21 05:22:35 +03:00
Devaev Maxim
66afbccf21 fixed segfault 2021-01-21 03:01:50 +03:00
Devaev Maxim
97dbe59aea handle usleep error 2021-01-21 02:56:43 +03:00
Devaev Maxim
d874fdeaec gitignore 2021-01-20 16:06:42 +03:00
Devaev Maxim
97e3938d56 trying to eliminate memory leaks 2021-01-20 16:06:32 +03:00
Devaev Maxim
87c7e8063f fixed shm umask 2021-01-20 15:17:13 +03:00
Devaev Maxim
fe5beb0114 fixed python destdir 2021-01-20 14:24:12 +03:00
Devaev Maxim
eec8e41b2b Bump version: 3.5 → 3.6 2021-01-20 14:03:48 +03:00
Devaev Maxim
87bff56a78 python module 2021-01-20 14:03:08 +03:00
Devaev Maxim
34e0e4dab4 moved flock_timedwait_monotonic() to tools.h 2021-01-20 12:36:37 +03:00
Devaev Maxim
e08ac1467f moved memsink_shared_s to separate file 2021-01-20 12:28:46 +03:00
Devaev Maxim
bc25e787cc buf fix 2021-01-20 12:27:28 +03:00
Devaev Maxim
5f11caf6fc added protocol version to memsink 2021-01-20 02:21:03 +03:00
Devaev Maxim
9be264e176 memsink magic 2021-01-18 11:27:57 +03:00
Devaev Maxim
3d28dcbaff improved memsink logic 2021-01-18 11:05:04 +03:00
Devaev Maxim
61c3b44c8a raw sink 2021-01-17 14:33:18 +03:00
Devaev Maxim
e26973a9f1 don't show help on option error 2021-01-17 09:37:44 +03:00
Devaev Maxim
598e2372e5 refactoring 2021-01-17 09:35:55 +03:00
Devaev Maxim
4fb8c7745c client ttl; some refactoring 2021-01-17 08:38:13 +03:00
Devaev Maxim
14131f0b54 check memsink clients 2021-01-17 00:06:44 +03:00
Devaev Maxim
de41c9653e pluggable outputs for the future 2021-01-15 12:38:32 +03:00
Devaev Maxim
66e0bb0a2c Bump version: 3.4 → 3.5 2021-01-15 01:30:17 +03:00
Devaev Maxim
5af18e8b70 fixed segfault on uninitialized mmal 2021-01-15 01:26:13 +03:00
Devaev Maxim
b746bc307c Bump version: 3.3 → 3.4 2021-01-13 17:46:08 +03:00
Devaev Maxim
3fd3aab909 banned option --as-needed from LDFLAGS 2021-01-13 17:45:33 +03:00
Devaev Maxim
2f1afb6044 Bump version: 3.2 → 3.3 2021-01-13 05:35:34 +03:00
Devaev Maxim
a016c1040e compatibility with freebsd install 2021-01-13 05:27:21 +03:00
Devaev Maxim
d4e9948220 Bump version: 3.1 → 3.2 2021-01-13 03:46:30 +03:00
Devaev Maxim
5d1183f5c6 http: zero_data option and X-UStreamer-Latency header 2021-01-13 03:45:45 +03:00
Devaev Maxim
c75863d4bd do not initialize omx on every startup 2021-01-13 03:30:31 +03:00
Devaev Maxim
5576cbb3b8 minor snprintf fixes 2021-01-13 01:26:11 +03:00
Devaev Maxim
4a156a692a Bump version: 3.0 → 3.1 2021-01-12 18:58:16 +03:00
Devaev Maxim
1a8dfb1f1b export h264 state 2021-01-12 15:28:42 +03:00
Devaev Maxim
d0a5246580 Bump version: 2.2 → 3.0 2021-01-12 01:42:42 +03:00
Devaev Maxim
0497e178ca fixed bumpversion 2021-01-12 01:42:08 +03:00
Devaev Maxim
3338dced5a mute 5 OMX_ErrorInsufficientResources 2021-01-12 01:32:35 +03:00
Devaev Maxim
b6d4a42fa7 refactoring 2021-01-12 00:35:28 +03:00
Devaev Maxim
e7b9ce500b fix 2021-01-11 15:14:10 +03:00
Devaev Maxim
d807f9fa87 force h264 keyframe on slowdown 2021-01-11 14:36:41 +03:00
Devaev Maxim
d95a6ad0b0 improved slowdown logic 2021-01-11 13:41:05 +03:00
Devaev Maxim
1e8a06b924 fix 2021-01-11 04:13:13 +03:00
Devaev Maxim
e72947ab8d fix 2021-01-11 04:12:14 +03:00
Devaev Maxim
4bedf7d286 enabled slowdown logic for sinks 2021-01-11 04:11:05 +03:00
Devaev Maxim
5aa9a4b7a0 last_id 2021-01-11 02:07:56 +03:00
Devaev Maxim
832915ce86 another log fix 2021-01-10 22:46:00 +03:00
Devaev Maxim
0e0c3ec023 log fix 2021-01-10 21:20:59 +03:00
Devaev Maxim
c75bd39e6a using hashes to enumerate frames in memsink 2021-01-10 19:21:29 +03:00
Devaev Maxim
d90cb1cff7 fixed alloc_size 2021-01-10 17:25:05 +03:00
Devaev Maxim
94dab648bc fix 2021-01-10 14:36:10 +03:00
Devaev Maxim
f37c1cf50c msg about h264 disabling 2021-01-10 14:31:21 +03:00
Devaev Maxim
6b9b19c077 lint fix 2021-01-10 14:27:17 +03:00
Devaev Maxim
c7d558dd6a security fix 2021-01-10 13:14:01 +03:00
Devaev Maxim
083ec30c66 renamed sink 2021-01-10 13:00:50 +03:00
Devaev Maxim
649cda2f47 h264 zero-copy 2021-01-10 00:40:39 +03:00
Devaev Maxim
302f1d297c fix 2021-01-09 16:39:04 +03:00
Devaev Maxim
cc06f2abad refactoring 2021-01-09 16:39:04 +03:00
Devaev Maxim
1e6c3b9708 refactoring 2021-01-09 15:08:34 +03:00
Devaev Maxim
310e30fdff deps tree 2021-01-09 01:03:27 +03:00
Devaev Maxim
8324b55396 fixed formatters 2021-01-08 03:39:29 +03:00
Devaev Maxim
9c679f6d5d refactoring 2021-01-08 03:33:14 +03:00
Devaev Maxim
f14f49dc92 fixed packages 2021-01-08 00:50:50 +03:00
Devaev Maxim
0a6c0335d0 help fix 2021-01-08 00:48:14 +03:00
Devaev Maxim
fedb1d4baf refactoring 2021-01-07 23:03:52 +03:00
Devaev Maxim
cfad2a8343 encoder only 2021-01-07 23:03:52 +03:00
Maxim Devaev
6969de4263 Merge pull request #81 from reedy/manpage-dump
Add upstreamer-dump manpage
2021-01-07 23:03:12 +03:00
Devaev Maxim
47994d5960 moved unjpeg from h264 to stream 2021-01-07 20:40:29 +03:00
Sam Reed
d77a7c74fb Install ustreamer-dump.1 during make install 2021-01-07 16:16:27 +00:00
Sam Reed
a2509158c6 Add upstreamer-dump manpage 2021-01-07 16:09:36 +00:00
Maxim Devaev
901146e5b4 Merge pull request #80 from reedy/patch-4
ustreamer-dump -o takes a filename argument
2021-01-07 19:01:24 +03:00
Sam Reed
35627fb64e ustreamer-dump -o takes a filename argument 2021-01-07 15:48:26 +00:00
Maxim Devaev
dbca5e29c0 Merge pull request #79 from reedy/patch-3
Update ustreamer.1
2021-01-07 18:47:20 +03:00
Sam Reed
ab9e37a1a9 Update ustreamer.1 2021-01-07 15:43:33 +00:00
Maxim Devaev
da057b2423 Merge pull request #78 from reedy/patch-2
Update copyright year in ustreamer help
2021-01-07 18:33:20 +03:00
Maxim Devaev
17c4f5a815 Merge pull request #77 from reedy/patch-1
Update copyright in ustreamer-dump help
2021-01-07 18:32:47 +03:00
Sam Reed
0e3143c1d5 Update copyright year in ustreamer help 2021-01-07 14:48:06 +00:00
Sam Reed
70084993d8 Update copyright in ustreamer-dump help 2021-01-07 14:46:44 +00:00
Devaev Maxim
ebe1d20e69 renamed workers 2021-01-07 17:45:29 +03:00
Devaev Maxim
6377830a35 simultaneous access to memsink 2021-01-07 17:36:35 +03:00
Devaev Maxim
2ab0f34add refactoring 2021-01-07 14:51:43 +03:00
Devaev Maxim
e176b1d738 improved prepare logic 2021-01-07 14:13:38 +03:00
Devaev Maxim
3199ef3b1d info about bitrate 2021-01-07 12:54:46 +03:00
Devaev Maxim
b16b447927 refactoring 2021-01-07 12:51:31 +03:00
Devaev Maxim
b924a0fecb configurable framerate and gop for h264 2021-01-07 12:19:59 +03:00
Devaev Maxim
7883625165 verbose latency 2021-01-07 11:09:58 +03:00
Devaev Maxim
14ec7741f9 update copy 2021-01-07 10:18:25 +03:00
Devaev Maxim
d05169d6d4 refactoring 2021-01-07 10:12:07 +03:00
Devaev Maxim
63c7d35b25 dump fourcc 2021-01-07 09:58:33 +03:00
Devaev Maxim
db00971622 fix 2021-01-07 09:47:29 +03:00
Devaev Maxim
d5275cacf7 dump as json 2021-01-07 09:44:39 +03:00
Devaev Maxim
0fbb41752e man directory 2021-01-06 22:49:26 +03:00
Devaev Maxim
5c904cf766 mute 2021-01-06 22:30:18 +03:00
Devaev Maxim
ac55b260ed updated manpage 2021-01-06 22:24:44 +03:00
Devaev Maxim
8207de6bd4 fix 2021-01-06 22:18:47 +03:00
Devaev Maxim
fb19858026 ustreamer-dump 2021-01-06 22:10:26 +03:00
Devaev Maxim
c81fa7b5a2 moved files 2021-01-06 18:52:54 +03:00
Devaev Maxim
fcee60346c jpeg sink 2021-01-06 18:47:14 +03:00
Devaev Maxim
a43d09ac73 refactoring 2021-01-06 15:50:19 +03:00
Devaev Maxim
2630147a96 aligned input 2021-01-05 21:26:07 +03:00
Devaev Maxim
17bb7c77f3 removed option --glitched-resolutions 2021-01-05 14:31:27 +03:00
Devaev Maxim
7d587052ad send logs to stderr 2021-01-05 09:56:01 +03:00
Devaev Maxim
85e63f49a0 refactoring 2021-01-04 20:38:14 +03:00
Devaev Maxim
dd90d378a4 mute 2021-01-04 17:32:34 +03:00
Devaev Maxim
19b93fb237 refactoring 2021-01-04 17:27:49 +03:00
Devaev Maxim
23292e9f42 fixed link 2021-01-04 14:06:09 +03:00
Devaev Maxim
511894e6ae added sps/pps frames 2021-01-04 13:58:35 +03:00
Devaev Maxim
e479a8f08c verb 2021-01-04 13:30:27 +03:00
Devaev Maxim
944bd89b4e h264 2021-01-04 12:55:25 +03:00
Devaev Maxim
6eb5e62aae don't recreate mmal wrapper 2021-01-03 15:45:15 +03:00
Devaev Maxim
5f5afb6f69 refactoring 2021-01-03 14:53:50 +03:00
Devaev Maxim
05b86c14a7 fixes 2021-01-03 13:00:22 +03:00
Devaev Maxim
3fdd69b444 refactoring 2021-01-03 12:27:26 +03:00
Devaev Maxim
0ccf540417 refactoring 2021-01-03 10:24:49 +03:00
Devaev Maxim
619389970a initializing global object in main() 2021-01-03 10:22:29 +03:00
Devaev Maxim
f7504211e5 512 kb 2021-01-03 08:01:01 +03:00
Devaev Maxim
1b1c546a55 mute 2021-01-03 07:58:42 +03:00
Devaev Maxim
6fadbb76d1 refactoring 2021-01-03 07:23:37 +03:00
Devaev Maxim
fa846d01d7 refactoring 2021-01-03 06:59:09 +03:00
Devaev Maxim
28deafaeef don't install recorder 2021-01-03 06:17:43 +03:00
Devaev Maxim
3fc9795ade fix 2021-01-02 19:08:11 +03:00
Devaev Maxim
22fd555454 reused unjpeg 2021-01-02 18:42:16 +03:00
Devaev Maxim
4fc022f4d7 fix 2021-01-02 18:01:16 +03:00
Devaev Maxim
5936830b28 refactoring 2021-01-02 17:58:23 +03:00
Devaev Maxim
7f089201d2 refactoring 2021-01-02 17:42:49 +03:00
Devaev Maxim
e21c39e172 refactoring 2021-01-02 17:18:35 +03:00
Devaev Maxim
1054b8c10f fix 2021-01-02 15:50:22 +03:00
Devaev Maxim
9002d8e445 separated workers pool 2021-01-02 15:43:52 +03:00
Devaev Maxim
61ef6ecd95 refactoring 2021-01-02 10:41:12 +03:00
Devaev Maxim
f1fe57109e set hw quality in device_open() 2021-01-02 10:35:36 +03:00
Devaev Maxim
daaefdd391 refactoring 2021-01-02 06:49:18 +03:00
Devaev Maxim
6687548ba9 refactoring 2021-01-02 06:12:44 +03:00
Devaev Maxim
8222c17aa7 refactoring 2021-01-01 15:30:19 +03:00
Devaev Maxim
0eed7f1b89 refactoring and test recorder 2021-01-01 15:04:12 +03:00
Devaev Maxim
5375781086 h264 encoder 2021-01-01 14:42:46 +03:00
Devaev Maxim
3090de6ff6 comparsion fix 2021-01-01 09:05:38 +03:00
Devaev Maxim
2ebd1e3d4a refactoring 2021-01-01 08:43:14 +03:00
Devaev Maxim
ee4b9c6338 frame_copy_meta() 2020-12-31 10:16:06 +03:00
Devaev Maxim
775bf32a6f refactoring 2020-12-28 07:09:52 +03:00
Devaev Maxim
e36d2bded3 refactoring 2020-12-28 06:24:47 +03:00
Devaev Maxim
0949c28658 unjpeg 2020-12-27 10:46:54 +03:00
Devaev Maxim
1e8789f5e5 keep pixel format 2020-12-27 04:46:36 +03:00
Devaev Maxim
01d0ed97de using frame_s for rawsink 2020-12-27 04:18:53 +03:00
Devaev Maxim
22d108f7ad using frame_s as common data storage 2020-12-27 03:10:52 +03:00
Devaev Maxim
3b223f5c49 refactoring 2020-12-27 02:41:47 +03:00
Devaev Maxim
c352ed7f67 lint fix 2020-12-18 19:38:16 +03:00
Devaev Maxim
ccf713dc1c lint fix 2020-12-17 19:03:35 +03:00
Devaev Maxim
0d97fffb3e pass online flag to rawsink 2020-12-14 13:19:55 +03:00
Devaev Maxim
cf5f284b95 expose blank to rawsink 2020-12-14 13:15:18 +03:00
Devaev Maxim
1837f502a7 refactoring 2020-12-13 18:31:17 +03:00
Devaev Maxim
89467a0ef9 using flock for rawsink 2020-12-11 22:19:41 +03:00
Devaev Maxim
0f92e73f56 lint fix 2020-12-10 14:33:24 +03:00
Devaev Maxim
b9e4975b77 refactoring 2020-12-10 14:03:53 +03:00
Devaev Maxim
7225857fcc refactoring 2020-12-10 12:36:35 +03:00
Devaev Maxim
f943f5927c meh 2020-12-09 20:47:41 +03:00
Devaev Maxim
f966907808 fix 2020-12-09 20:34:09 +03:00
Devaev Maxim
983cb899ec man fix 2020-12-09 19:10:24 +03:00
Devaev Maxim
9e1bf2fdea using uint8_t 2020-12-09 19:06:14 +03:00
Devaev Maxim
f19ab11f76 refactoring 2020-12-09 18:44:12 +03:00
Devaev Maxim
9039aa8ac5 refactoring 2020-12-08 17:17:10 +03:00
Devaev Maxim
8fc11ac056 refactoring 2020-12-08 15:51:32 +03:00
Devaev Maxim
eebd8307c5 refactoring 2020-12-08 15:39:53 +03:00
Devaev Maxim
0d006cffa9 noop delay 2020-12-08 14:17:46 +03:00
Devaev Maxim
d94bb948eb fix 2020-12-08 12:45:47 +03:00
Devaev Maxim
5ded791ef0 quality=0 for noop encoder 2020-12-08 12:42:23 +03:00
Devaev Maxim
0ccb54b4f0 refactoring 2020-12-08 11:43:35 +03:00
Devaev Maxim
b3ad29c0c7 one encoder for noop 2020-12-08 11:17:38 +03:00
Devaev Maxim
dd86e8cb42 refactoring 2020-12-08 10:38:42 +03:00
Devaev Maxim
f7ddb635a5 refactoring 2020-12-08 10:32:10 +03:00
Devaev Maxim
2fd3bc34b5 refactoring 2020-12-08 08:13:56 +03:00
Devaev Maxim
283eba0666 refactoring 2020-12-08 07:58:53 +03:00
Devaev Maxim
5c48faa832 refactoring 2020-12-08 07:28:35 +03:00
Devaev Maxim
7bb0aae71e noop encoder 2020-12-07 22:00:41 +03:00
Devaev Maxim
07b712a46b refactoring 2020-12-07 21:06:44 +03:00
Devaev Maxim
5e18ce3806 rawsink slave 2020-12-07 20:31:04 +03:00
Devaev Maxim
e7ad86ded9 refactoring 2020-12-07 19:35:37 +03:00
Devaev Maxim
338389c219 raw sink 2020-12-07 18:06:20 +03:00
Devaev Maxim
847726c0d7 refactoring 2020-12-07 13:26:27 +03:00
Devaev Maxim
a9d50a2a74 refactoring 2020-12-07 11:58:02 +03:00
Devaev Maxim
720baf09b5 codebase refactoring 2020-12-06 18:45:10 +03:00
Devaev Maxim
b502714281 style fix 2020-12-05 03:32:38 +03:00
Devaev Maxim
92c8215d3d install manpage on gentoo 2020-12-05 03:32:02 +03:00
Devaev Maxim
348849da96 improved manpage 2020-12-05 03:29:15 +03:00
Devaev Maxim
e845d53940 install manpage 2020-12-03 16:33:29 +03:00
Devaev Maxim
c999f59ddd python bump 2020-12-03 07:06:43 +03:00
Maxim Devaev
ee144473c1 Merge pull request #64 from reedy/manpage
Create initial ustreamer manpage
2020-12-03 06:57:28 +03:00
Sam Reed
af325ed54e Add Usage section to manpage
Based on https://github.com/pikvm/ustreamer/blob/f8e26d7/README.md#usage
2020-12-01 22:53:52 +00:00
Sam Reed
0d363791fe Support ustreamer.1 manpage in bumpversion 2020-12-01 22:17:32 +00:00
Maxim Devaev
814a2eb641 Merge pull request #66 from reedy/patch-1
s/bellow/below/
2020-12-01 23:54:28 +03:00
Sam Reed
73c22fa960 s/bellow/below/ 2020-12-01 20:42:13 +00:00
Maxim Devaev
f6f9a12789 Merge pull request #63 from reedy/patch-1
Add trailing . for consistency
2020-12-01 21:53:45 +03:00
Maxim Devaev
b14c53cd10 Merge pull request #65 from reedy/patch-2
Create .gitattributes
2020-12-01 21:52:21 +03:00
Sam Reed
23164f2c16 Create .gitattributes
Removes a few files unnecessary from the source tarball
2020-12-01 16:54:19 +00:00
Sam Reed
8e7d21c1b5 Create initial ustreamer manpage 2020-12-01 16:42:46 +00:00
Sam Reed
973d1cc10e Add trailing . for consistency 2020-12-01 15:55:57 +00:00
Devaev Maxim
3bc4afca9d Bump version: 2.1 → 2.2 2020-11-27 03:00:23 +03:00
Devaev Maxim
f43afababa Merge branch 'semwait' 2020-11-27 01:52:58 +03:00
Devaev Maxim
1b2de09438 CFG_OMX_SEMWAIT_TIMEOUT 2020-11-27 01:46:33 +03:00
Maxim Devaev
b0c54b18a5 Update README.ru.md 2020-11-26 22:42:39 +03:00
Maxim Devaev
f8e26d785f Update README.md 2020-11-26 22:42:10 +03:00
Devaev Maxim
28563abdbc Issue #56: busyloop-based _vcos_semwait() 2020-11-13 17:02:00 +03:00
Devaev Maxim
f1a869a215 lint fix 2020-11-13 11:48:49 +03:00
Maxim Devaev
9778a805ca Merge pull request #57 from mtlynch/fix-typos
Fix two typos in log messages
2020-11-13 11:47:26 +03:00
Michael Lynch
a008dcf99d Fix typo: quierying -> querying
This fixes a small typo in a log message from 'quierying' to 'querying'.
2020-11-12 17:26:00 -05:00
Michael Lynch
71c64e668d Fix typo in log message: EINVAL
Fixes a minor typo in a logging message from EINTVAL to EINVAL.
2020-11-12 15:38:49 -05:00
Devaev Maxim
d9b91a1d5f Bump version: 2.0 → 2.1 2020-09-29 01:42:40 +03:00
Devaev Maxim
d682a1c173 added info about mjpg-streamer compat and prettify 2020-09-28 13:54:38 +03:00
Devaev Maxim
ba03333623 better access checking 2020-09-28 10:28:42 +03:00
Devaev Maxim
c7e6e5e006 check for device access before open 2020-09-28 04:29:02 +03:00
Devaev Maxim
45b1e2f285 refactoring 2020-09-28 04:23:41 +03:00
Devaev Maxim
d9bbd8a74d refactoring 2020-09-27 22:58:13 +03:00
Devaev Maxim
37179184ae Bump version: 1.26 → 2.0 2020-09-25 00:03:14 +03:00
Devaev Maxim
fc8aba0a12 Bump version: 1.25 → 1.26 2020-09-25 00:01:13 +03:00
Devaev Maxim
0d749eada3 V for make release 2020-09-23 18:11:25 +03:00
Maxim Devaev
da6984d531 Merge pull request #49 from pikvm/allow-origin
Disabled cross-domain requests by default
2020-09-23 00:04:45 +03:00
Devaev Maxim
df14031042 readme fix 2020-09-22 21:43:01 +03:00
Maxim Devaev
03975c1a85 Update README.md 2020-09-22 18:02:27 +03:00
Maxim Devaev
214a924da3 Update README.ru.md 2020-09-22 18:01:48 +03:00
Maxim Devaev
9e6a9a2fd4 Update README.md 2020-09-22 17:59:09 +03:00
Devaev Maxim
b498ae7e38 Issue #48: Disabled cross-domain requests by default 2020-09-22 17:58:10 +03:00
Maxim Devaev
278645ce51 Merge pull request #47 from pikvm/queue-assert
Double dequeue error
2020-09-20 00:53:58 +03:00
Devaev Maxim
f1ee5514e3 style fix 2020-09-20 00:44:21 +03:00
Maxim Devaev
3900728f9f Merge pull request #46 from pikvm/libgpiod
moved from wiringpi to libgpiod
2020-09-19 17:01:28 +03:00
Devaev Maxim
3dc083d2ef Rewrited #44: fixed memory error and leak 2020-09-19 16:55:39 +03:00
Maxim Devaev
653ebd6e88 Merge pull request #44 from schneemaier/master
Added HTTP GET parameter handling to server.c
2020-09-19 13:29:07 +03:00
Devaev Maxim
a770e7675d Issue #43: assert for double VIDIOC_DQBUF 2020-09-19 05:54:47 +03:00
Devaev Maxim
6725083be6 moved from wiringpi to libgpiod 2020-09-19 04:06:14 +03:00
Akos Schneemaier
0b39cadaad Added HTTP GET parameter handling to server.c to make URLcompatibility with mjpg streamer 2020-09-18 00:00:11 -04:00
Devaev Maxim
871b0cf132 improved logs 2020-09-18 04:19:58 +03:00
Maxim Devaev
afa888432a Update README.ru.md 2020-09-15 23:07:22 +03:00
Maxim Devaev
a42bd147ff Update README.md 2020-09-15 23:06:47 +03:00
Devaev Maxim
2ad8871a54 Bump version: 1.24 → 1.25 2020-09-01 08:55:28 +03:00
Devaev Maxim
266e210b04 fixed ld error 2020-09-01 08:53:33 +03:00
Devaev Maxim
0ac9f77619 Bump version: 1.23 → 1.24 2020-09-01 07:57:09 +03:00
Devaev Maxim
c1bc1d9506 fixed linter error 2020-09-01 07:52:29 +03:00
Devaev Maxim
deb37986b6 Issue #39: fixed missing gettid() syscall on *BSD 2020-09-01 06:39:12 +03:00
Devaev Maxim
ee6c555ce0 Issue #32: refactoring 2020-08-24 10:21:47 +03:00
Maxim Devaev
4395b8487f Merge pull request #37 from PascalHonegger/master
Issue #32: Create Dockerfiles
2020-08-24 00:22:52 +03:00
Pascal Honegger
f622d03d1b Issue #32: Create Dockerfiles 2020-08-23 15:36:48 +02:00
Devaev Maxim
36e6fa7b09 added aarch64 to PKGBUILD 2020-08-23 15:56:59 +03:00
Devaev Maxim
8cf6c66f21 Fixed #35: spell fix 2020-08-22 23:32:06 +03:00
Devaev Maxim
ac9761beb2 Bump version: 1.22 → 1.23 2020-08-22 16:53:29 +03:00
Devaev Maxim
90b7a5600f Issue #24: disable software framedrop if hw_fps == desired_fps 2020-08-22 16:17:34 +03:00
Maxim Devaev
4c70baecb1 Update README.ru.md 2020-08-21 23:01:15 +03:00
Maxim Devaev
15c14bfebf Update README.ru.md 2020-08-21 23:00:14 +03:00
Maxim Devaev
eab8043496 Update README.md 2020-08-21 22:59:11 +03:00
Devaev Maxim
53feba1248 Bump version: 1.21 → 1.22 2020-08-20 05:15:24 +03:00
Devaev Maxim
119821d5af queued_fps = 0 for no clients 2020-08-19 14:27:52 +03:00
Maxim Devaev
4faabf27ec Merge pull request #33 from pikvm/sem-timeout
Sem timeout
2020-08-19 13:44:23 +03:00
Devaev Maxim
191f6e3c09 non-zero min-frame-size; default = 128 2020-08-19 13:20:22 +03:00
Devaev Maxim
4e51439118 bsd compat 2020-08-18 15:19:09 +03:00
Devaev Maxim
e184e187a2 option --color-effect 2020-08-18 12:15:44 +03:00
Maxim Devaev
592568c9aa Merge pull request #31 from pikvm/flip
Options to flip image
2020-08-17 18:17:01 +03:00
Devaev Maxim
46c5a547a9 options to flip image 2020-08-17 03:49:29 +03:00
Devaev Maxim
3d097a4ffb more logs 2020-08-15 04:51:00 +03:00
Devaev Maxim
00e32c915c fixed uninitialized value 2020-08-15 04:40:55 +03:00
Devaev Maxim
d44c340dce vcos sem timeout 2020-08-15 00:10:07 +03:00
Devaev Maxim
8c18f1dffe Issue #25: fixed freebsd build 2020-08-14 03:38:05 +03:00
Devaev Maxim
c3c386ea5b Bump version: 1.20 → 1.21 2020-08-13 08:59:36 +03:00
Devaev Maxim
fa09992c46 better logging 2020-08-11 06:08:13 +03:00
Devaev Maxim
cefcd0c963 fixed ptr printing 2020-08-11 05:49:03 +03:00
Devaev Maxim
96c806071d Bump version: 1.19 → 1.20 2020-08-11 02:13:16 +03:00
Devaev Maxim
0775b35ef8 option --tcp-nodelay 2020-08-10 08:12:14 +03:00
Maxim Devaev
138d9a74d8 Update README.ru.md 2020-08-01 14:34:15 +03:00
Maxim Devaev
2a668643dc Update README.md 2020-08-01 14:33:52 +03:00
Maxim Devaev
56312cffb5 Update README.md 2020-07-24 13:08:53 +03:00
Maxim Devaev
f553b97dba Update README.md 2020-07-23 22:19:12 +03:00
Maxim Devaev
b619b1e096 Update README.ru.md 2020-07-23 22:18:52 +03:00
Maxim Devaev
06a32fd3ab Update README.md 2020-07-23 22:18:20 +03:00
102 changed files with 7072 additions and 3751 deletions

View File

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

6
.dockerignore Normal file
View File

@@ -0,0 +1,6 @@
# Ignore everything
*
# Allow source code
!Makefile
!src/**

5
.gitattributes vendored Normal file
View File

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

3
.gitignore vendored
View File

@@ -6,7 +6,10 @@
/pkg/arch/ustreamer-*.pkg.tar.xz
/pkg/arch/ustreamer-*.pkg.tar.zst
/build/
/python/build/
/config.mk
/vgcore.*
/ustreamer
/ustreamer-dump
/*.so
/*.sock

113
Makefile
View File

@@ -1,11 +1,14 @@
-include config.mk
PROG ?= ustreamer
USTR ?= ustreamer
DUMP ?= ustreamer-dump
DESTDIR ?=
PREFIX ?= /usr/local
MANPREFIX ?= $(PREFIX)/share/man
CC ?= gcc
CFLAGS ?= -O3
PY ?= python3
CFLAGS ?= -O3 -MD
LDFLAGS ?=
RPI_VC_HEADERS ?= /opt/vc/include
@@ -13,13 +16,29 @@ RPI_VC_LIBS ?= /opt/vc/lib
BUILD ?= build
LINTERS_IMAGE ?= $(PROG)-linters
LINTERS_IMAGE ?= $(USTR)-linters
# =====
_LIBS = -lm -ljpeg -pthread -levent -levent_pthreads -luuid
override CFLAGS += -c -std=c11 -Wall -Wextra -D_GNU_SOURCE
_SRCS = $(shell ls src/*.c src/http/*.c src/encoders/cpu/*.c src/encoders/hw/*.c)
_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
@@ -28,15 +47,19 @@ endef
ifneq ($(call optbool,$(WITH_OMX)),)
_LIBS += -lbcm_host -lvcos -lopenmaxil -L$(RPI_VC_LIBS)
_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)
_SRCS += $(shell ls src/encoders/omx/*.c)
_USTR_SRCS += $(shell ls \
src/ustreamer/encoders/omx/*.c \
src/ustreamer/h264/*.c \
)
endif
ifneq ($(call optbool,$(WITH_GPIO)),)
_LIBS += -lwiringPi
_USTR_LIBS += -lgpiod
override CFLAGS += -DWITH_GPIO
_USTR_SRCS += $(shell ls src/ustreamer/gpio/*.c)
endif
@@ -49,41 +72,57 @@ endif
WITH_SETPROCTITLE ?= 1
ifneq ($(call optbool,$(WITH_SETPROCTITLE)),)
ifeq ($(shell uname -s | tr A-Z a-z),linux)
_LIBS += -lbsd
_USTR_LIBS += -lbsd
endif
override CFLAGS += -DWITH_SETPROCTITLE
endif
# =====
all: $(PROG)
all: $(USTR) $(DUMP) python
install: $(PROG)
install -Dm755 $(PROG) $(DESTDIR)$(PREFIX)/bin/$(PROG)
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 -f $(DESTDIR)$(MANPREFIX)/man1/$(USTR).1
gzip -f $(DESTDIR)$(MANPREFIX)/man1/$(DUMP).1
ifneq ($(call optbool,$(WITH_PYTHON)),)
cd python && $(PY) setup.py install --prefix=$(PREFIX) --root=$(if $(DESTDIR),$(DESTDIR),/)
endif
install-strip: install
strip $(DESTDIR)$(PREFIX)/bin/$(PROG)
uninstall:
rm $(DESTDIR)$(PREFIX)/bin/$(PROG)
strip $(DESTDIR)$(PREFIX)/bin/$(USTR)
strip $(DESTDIR)$(PREFIX)/bin/$(DUMP)
regen:
tools/make-jpeg-h.py src/http/data/blank.jpeg src/http/data/blank_jpeg.h BLANK
tools/make-html-h.py src/http/data/index.html src/http/data/index_html.h INDEX
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
$(PROG): $(_SRCS:%.c=$(BUILD)/%.o)
$(info -- LD $@)
@ $(CC) $^ -o $@ $(LDFLAGS) $(_LIBS)
$(info ===== Build complete =====)
$(info == CC = $(CC))
$(info == LIBS = $(_LIBS))
$(info == CFLAGS = $(CFLAGS))
$(info == LDFLAGS = $(LDFLAGS))
$(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
@@ -92,11 +131,20 @@ $(BUILD)/%.o: %.c
@ $(CC) $< -o $@ $(CFLAGS)
python:
ifneq ($(call optbool,$(WITH_PYTHON)),)
$(info == PY_BUILD ustreamer-*.so)
@ cd python && $(PY) setup.py build
@ ln -sf python/build/lib.*/*.so .
else
@ true
endif
release:
make clean
make tox
make push
make bump
make bump V=$(V)
make push
make clean
@@ -134,6 +182,11 @@ clean-all: linters clean
-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 $(PROG) $(BUILD) vgcore.* *.sock
rm -rf $(USTR) $(DUMP) $(BUILD) python/build vgcore.* *.sock *.so
.PHONY: linters
.PHONY: python linters
_OBJS = $(_USTR_SRCS:%.c=$(BUILD)/%.o) $(_DUMP_SRCS:%.c=$(BUILD)/%.o)
-include $(_OBJS:%.o=%.d)

View File

@@ -4,8 +4,8 @@
[[Русская версия]](README.ru.md)
µStreamer is a lightweight and very quick server to broadcast [MJPG](https://en.wikipedia.org/wiki/Motion_JPEG) video from any V4L2 device to the net. All new browsers have native support of this video format, as well as most video players such as mplayer, VLC etc.
µStreamer is a part of the [Pi-KVM](https://github.com/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 [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 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:
@@ -13,19 +13,20 @@
|----------|---------------|-------------------|
| Multithreaded JPEG encoding | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| [OpenMAX IL](https://www.khronos.org/openmaxil) hardware acceleration<br>on Raspberry Pi | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Behavior when the device<br>is disconnected while streaming | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Shows a black screen<br>with ```NO SIGNAL``` on it<br>until reconnected | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Stops the broadcast <sup>1</sup> |
| [DV-timings](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) support -<br>the ability to change resolution<br>on the fly by source signal | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) Partially yes <sup>1</sup> |
| 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 broadcast parameters | ![#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 to GPIO<br>on Raspberry Pi using [wiringPi](http://wiringpi.com) | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| 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 | :) |
Footnotes:
* ```1``` Long before µStreamer, I made a [patch](https://github.com/jacksonliam/mjpg-streamer/pull/164) to add DV-timings support to mjpg-streamer and to keep it from hanging up no device disconnection. Alas, the patch is far from perfect and I can't guarantee it will work every time - mjpg-streamer's source code is very complicated and its structure is hard to understand. With this in mind, along with needing multithreading and JPEG hardware acceleration in the future, I decided to make my own stream server from scratch instead of supporting legacy code.
* ```2``` This feature allows to cut down outgoing traffic several-fold when broadcasting HDMI, but it increases CPU usage a little bit. The idea is that HDMI is a fully digital interface and each captured frame can be identical to the previous one byte-wise. There's no need to broadcast the same image over the net several times a second. With the `--drop-same-frames=20` option enabled, µStreamer will drop all the matching frames (with a limit of 20 in a row). Each new frame is matched with the previous one first by length, then using ```memcmp()```.
* ```2``` This feature allows to cut down outgoing traffic several-fold when streaming HDMI, but it increases CPU usage a little bit. The idea is that HDMI is a fully digital interface and each captured frame can be identical to the previous one byte-wise. There's no need to stream the same image over the net several times a second. With the `--drop-same-frames=20` option enabled, µStreamer will drop all the matching frames (with a limit of 20 in a row). Each new frame is matched with the previous one first by length, then using ```memcmp()```.
-----
# TL;DR
@@ -36,10 +37,11 @@ If you're going to live-stream from your backyard webcam and need to control it,
You'll need ```make```, ```gcc```, ```libevent``` with ```pthreads``` support, ```libjpeg8```/```libjpeg-turbo```, ```libuuid``` 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`.
* 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`.
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 [wiringPi](http://wiringpi.com) 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```.
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```.
```
$ git clone --depth=1 https://github.com/pikvm/ustreamer
@@ -48,16 +50,18 @@ $ 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```. Same with GPIO.
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```.
FreeBSD port: https://www.freshports.org/multimedia/ustreamer.
-----
# Usage
Without arguments, ```ustreamer``` will try to open ```/dev/video0``` with 640x480 resolution and start broadcasting on ```http://127.0.0.1:8080```. You can override this behavior using parameters ```--device```, ```--host``` and ```--port```. For example, to broadcast to the world, run:
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
```
:exclamation: Please note that since µStreamer v2.0 cross-domain requests were disabled by default for [security reasons](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). To enable the old behavior, use the option `--allow-origin=\*`.
The recommended way of running µStreamer with [Auvidea B101](https://www.raspberrypi.org/forums/viewtopic.php?f=38&t=120702&start=400#p1339178) on Raspberry Pi:
```bash
$ ./ustreamer \
@@ -69,6 +73,8 @@ $ ./ustreamer \
--drop-same-frames=30 # Save the traffic
```
:exclamation: Please note that to use `--drop-same-frames` for different browsers you need to use some specific URL `/stream` parameters (see URL `/` for details).
You can always view the full list of options with ```ustreamer --help```.
-----
@@ -78,7 +84,7 @@ You can always view the full list of options with ```ustreamer --help```.
-----
# License
Copyright (C) 2018 by Maxim Devaev mdevaev@gmail.com
Copyright (C) 2018-2021 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,7 +5,7 @@
[[English version]](README.md)
µStreamer - это маленький и очень быстрый сервер, который позволяет организовать трансляцию видео в формате [MJPG](https://en.wikipedia.org/wiki/Motion_JPEG) с любого устройства V4L2 в сеть. Этот формат нативно поддерживается всеми современными браузерами и большинством приложений для просмотра видео (mplayer, VLC и так далее). µStreamer был разработан в рамках проекта [Pi-KVM](https://github.com/pikvm) специально для стриминга с устройств видеозахвата [VGA](https://www.amazon.com/dp/B0126O0RDC) и [HDMI](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/) с максимально возможным разрешением и FPS, которые только позволяет железо.
µ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 очень похож на [mjpg-streamer](https://github.com/jacksonliam/mjpg-streamer) при использовании им плагинов ```input_uvc.so``` и ```output_http.so```, однако имеет ряд серьезных отличий. Основные приведены в этой таблице:
@@ -14,13 +14,14 @@
| Многопоточное кодирование JPEG | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Аппаратное кодирование с помощью [OpenMAX IL](https://www.khronos.org/openmaxil) на Raspberry Pi | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Поведение при физическом отключении<br>устройства от сервера во время работы | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Транслирует черный экран<br>с надписью ```NO SIGNAL```,<br>пока устройство не будет подключено снова | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Прерывает трансляцию <sup>1</sup> |
| Поддержка [DV-таймингов](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) - возможности<br>изменения параметров разрешения<br>трансляции на лету по сигналу<br>источника (устройства видеозахвата) | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#ffaa00](https://placehold.it/15/ffaa00/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>на Raspberry Pi с помощью [wiringPi](http://wiringpi.com) | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/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=+) Есть | :) |
Сносочки:
* ```1``` Еще до написания µStreamer, я запилил [патч](https://github.com/jacksonliam/mjpg-streamer/pull/164), добавляющий в mjpg-streamer поддержку DV-таймингов и предотвращающий его зависание при отключении устройства. Однако патч, увы, далек от совершенства и я не гарантирую его стопроцентную работоспособность, поскольку код mjpg-streamer чрезвычайно запутан и очень плохо структурирован. Учитывая это, а также то, что в дальнейшем мне потребовались многопоточность и аппаратное кодирование JPEG, было принято решение написать свой стрим-сервер с нуля, чтобы не тратить силы на поддержку лишнего легаси.
@@ -36,10 +37,11 @@
Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads```, ```libjpeg8```/```libjpeg-turbo```, ```libuuid``` и ```libbsd``` (только для Linux).
* Arch: `sudo pacman -S libevent libjpeg-turbo libutil-linux libbsd`.
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev uuid-dev libbsd-dev`.
* 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`.
На Raspberry Pi программу можно собрать с поддержкой OpenMAX IL. Для этого передайте ```make``` параметр ```WITH_OMX=1```. Для включения сборки с поддержкой GPIO установите [wiringPi](http://wiringpi.com) и добавьте параметр ```WITH_GPIO=1```. Если при сборке компилятор ругается на отсутствие функции ```pthread_get_name_np()``` или другой подобной, добавьте параметр ```WITH_PTHREAD_NP=0``` (по умолчанию он включен). При аналогичной ошибке с функцией ```setproctitle()``` добавьте параметр ```WITH_SETPROCTITLE=0```.
На 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```.
```
$ git clone --depth=1 https://github.com/pikvm/ustreamer
@@ -48,7 +50,7 @@ $ make
$ ./ustreamer --help
```
Для Arch Linux в AUR есть готовый пакет: https://aur.archlinux.org/packages/ustreamer. На Raspberry Pi програма автоматически собирается с поддержкой OpenMAX IL, если обнаружит нужные хедеры в ```/opt/vc/include```. То же самое и с GPIO.
Для Arch Linux в AUR есть готовый пакет: https://aur.archlinux.org/packages/ustreamer. На Raspberry Pi програма автоматически собирается с поддержкой OpenMAX IL, если обнаружит нужные хедеры в ```/opt/vc/include```.
Порт для FreeBSD: https://www.freshports.org/multimedia/ustreamer.
-----
@@ -58,6 +60,8 @@ $ ./ustreamer --help
# ./ustreamer --device=/dev/video1 --host=0.0.0.0 --port=80
```
:exclamation: Обратите внимание, что начиная с версии µStreamer v2.0 кросс-доменные запросы были выключены по умолчанию [по соображениям безопасности](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). Чтобы включить старое поведение, используйте опцию `--allow-origin=\*`.
Рекомендуемый способ запуска µStreamer для работы с [Auvidea B101](https://www.raspberrypi.org/forums/viewtopic.php?f=38&t=120702&start=400#p1339178) на Raspberry Pi:
```bash
$ ./ustreamer \
@@ -69,6 +73,8 @@ $ ./ustreamer \
--drop-same-frames=30 # Экономим трафик
```
:exclamation: Обратите внимание, что для использования `--drop-same-frames` для разных браузеров нужно использовать ряд специальных параметров в `/stream` (за деталями обратитесь к урлу `/`).
За полным списком опций обращайтесь ко встроенной справке: ```ustreamer --help```.
-----
@@ -78,7 +84,7 @@ $ ./ustreamer \
-----
# Лицензия
Copyright (C) 2018 by Maxim Devaev mdevaev@gmail.com
Copyright (C) 2018-2021 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

@@ -16,6 +16,9 @@ RUN pacman -Syu --noconfirm \
python-pip \
python-tox \
cppcheck \
npm \
&& (pacman -Sc --noconfirm || true)
RUN npm install htmlhint -g
CMD /bin/bash

View File

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

View File

@@ -1,12 +1,12 @@
[tox]
envlist = cppcheck, flake8, pylint, mypy, vulture
envlist = cppcheck-src, cppcheck-python, flake8, pylint, mypy, vulture, htmlhint
skipsdist = true
[testenv]
basepython = python3.8
basepython = python3.9
changedir = /src
[testenv:cppcheck]
[testenv:cppcheck-src]
whitelist_externals = cppcheck
commands = cppcheck \
--force \
@@ -17,30 +17,52 @@ commands = cppcheck \
--suppress=assignmentInAssert \
--suppress=variableScope \
--inline-suppr \
--library=python \
-DCHAR_BIT=8 \
src
-DWITH_OMX \
-DWITH_GPIO \
src python
[testenv:cppcheck-python]
whitelist_externals = cppcheck
commands = cppcheck \
--force \
--std=c11 \
--error-exitcode=1 \
--quiet \
--enable=warning,unusedFunction,portability,performance,style \
--suppress=assignmentInAssert \
--suppress=variableScope \
--inline-suppr \
--library=python \
-DCHAR_BIT=8 \
python
[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
[testenv:htmlhint]
whitelist_externals = htmlhint
commands = htmlhint src/ustreamer/http/data/*.html

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

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

314
man/ustreamer.1 Normal file
View File

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

View File

@@ -3,29 +3,40 @@
pkgname=ustreamer
pkgver=1.19
pkgver=3.8
pkgrel=1
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
url="https://github.com/pikvm/ustreamer"
license=(GPL)
arch=(i686 x86_64 armv6h armv7h)
depends=(libjpeg libevent libutil-linux libbsd)
# optional: raspberrypi-firmware for OMX encoder
# optional: wiringpi for GPIO support
arch=(i686 x86_64 armv6h armv7h aarch64)
depends=(libjpeg libevent libutil-linux libbsd libgpiod)
makedepends=(gcc make)
source=(${pkgname}::"git+https://github.com/pikvm/ustreamer#commit=v${pkgver}")
md5sums=(SKIP)
_options="WITH_GPIO=1"
if [ -e /usr/bin/python3 ]; then
_options="$_options WITH_PYTHON=1"
depends+=(python)
makedepends+=(python-setuptools)
fi
if [ -e /opt/vc/include/IL/OMX_Core.h ]; then
depends+=(raspberrypi-firmware)
makedepends+=(raspberrypi-firmware)
_options="$_options WITH_OMX=1"
fi
build() {
cd "$srcdir"
rm -rf $pkgname-build
cp -r $pkgname $pkgname-build
cd $pkgname-build
local _options=""
[ -e /opt/vc/include/IL/OMX_Core.h ] && _options="$_options WITH_OMX=1"
[ -e /usr/include/wiringPi.h ] && _options="$_options WITH_GPIO=1"
# LD does not link mmal with this option
LDFLAGS="${LDFLAGS//--as-needed/}"
LDFLAGS="${LDFLAGS//,,/,}"
make $_options CFLAGS="$CFLAGS" LDFLAGS="$LDFLAGS" $MAKEFLAGS
}

View File

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

View File

@@ -0,0 +1,34 @@
FROM balenalib/raspberrypi3-debian:build as build
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
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
FROM balenalib/raspberrypi3-debian:run as RUN
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
libevent-2.1 \
libevent-pthreads-2.1-6 \
libjpeg8 \
uuid \
libbsd0 \
libgpiod2 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /ustreamer
COPY --from=build /build/ustreamer/ustreamer .
EXPOSE 8080
ENTRYPOINT ["./ustreamer", "--host=::"]
# vim: syntax=dockerfile

View File

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

View File

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

View File

@@ -6,7 +6,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=ustreamer
PKG_VERSION:=1.19
PKG_VERSION:=3.8
PKG_RELEASE:=1
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
@@ -36,6 +36,7 @@ endef
define Package/ustreamer/install
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/ustreamer $(1)/usr/bin/
$(INSTALL_BIN) $(PKG_BUILD_DIR)/ustreamer-dump $(1)/usr/bin/
$(INSTALL_DIR) $(1)/etc/config
$(CP) ./files/ustreamer.config $(1)/etc/config/ustreamer
$(INSTALL_DIR) $(1)/etc/init.d

26
python/setup.py Normal file
View File

@@ -0,0 +1,26 @@
from distutils.core import Extension
from distutils.core import setup
# =====
if __name__ == "__main__":
setup(
name="ustreamer",
version="3.8",
description="uStreamer tools",
author="Maxim Devaev",
author_email="mdevaev@gmail.com",
url="https://github.com/pikvm/ustreamer",
ext_modules=[
Extension(
"ustreamer",
libraries=["rt", "m", "pthread"],
undef_macros=["NDEBUG"],
sources=["ustreamer.c"],
depends=[
"../src/libs/tools.h",
"../src/libs/memsinksh.h",
],
),
],
)

325
python/ustreamer.c Normal file
View File

@@ -0,0 +1,325 @@
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <Python.h>
#include "../src/libs/tools.h" // Just a header without C-sources
#include "../src/libs/memsinksh.h" // No sources again
typedef struct {
PyObject_HEAD
char *obj;
double lock_timeout;
double wait_timeout;
int fd;
memsink_shared_s *mem;
uint8_t *tmp_data;
size_t tmp_data_allocated;
uint64_t last_id;
PyObject *frame; // PyDict
} MemsinkObject;
static void MemsinkObject_destroy_internals(MemsinkObject *self) {
if (self->frame != NULL) {
Py_DECREF(self->frame);
self->frame = NULL;
}
if (self->mem != NULL) {
munmap(self->mem, sizeof(memsink_shared_s));
self->mem = NULL;
}
if (self->fd > 0) {
close(self->fd);
self->fd = -1;
}
if (self->tmp_data) {
free(self->tmp_data);
self->tmp_data = NULL;
self->tmp_data_allocated = 0;
}
}
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", NULL};
if (!PyArg_ParseTupleAndKeywords(
args, kwargs, "s|dd", kws,
&self->obj, &self->lock_timeout, &self->wait_timeout)) {
return -1;
}
# define SET_TIMEOUT(_timeout) { \
if (self->_timeout <= 0) { \
PyErr_SetString(PyExc_ValueError, #_timeout " must be > 0"); \
return -1; \
} \
}
SET_TIMEOUT(lock_timeout);
SET_TIMEOUT(wait_timeout);
# undef CHECK_TIMEOUT
self->tmp_data_allocated = 512 * 1024;
A_REALLOC(self->tmp_data, self->tmp_data_allocated);
if ((self->fd = shm_open(self->obj, O_RDWR, 0)) == -1) {
PyErr_SetFromErrno(PyExc_OSError);
goto error;
}
if ((self->mem = mmap(
NULL,
sizeof(memsink_shared_s),
PROT_READ | PROT_WRITE,
MAP_SHARED,
self->fd,
0
)) == MAP_FAILED) {
PyErr_SetFromErrno(PyExc_OSError);
self->mem = NULL;
goto error;
}
if (self->mem == NULL) {
PyErr_SetString(PyExc_RuntimeError, "Memory mapping is NULL"); \
goto error;
}
if ((self->frame = PyDict_New()) == NULL) {
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", "");
}
#define MEM(_next) self->mem->_next
static int wait_frame(MemsinkObject *self) {
long double deadline_ts = get_now_monotonic() + self->wait_timeout;
# define RETURN_OS_ERROR { \
Py_BLOCK_THREADS \
PyErr_SetFromErrno(PyExc_OSError); \
return -1; \
}
do {
Py_BEGIN_ALLOW_THREADS
int retval = flock_timedwait_monotonic(self->fd, self->lock_timeout);
if (retval < 0 && errno != EWOULDBLOCK) {
RETURN_OS_ERROR;
} else if (retval == 0) {
if (MEM(magic) == MEMSINK_MAGIC && MEM(version) == MEMSINK_VERSION && MEM(id) != self->last_id) {
Py_BLOCK_THREADS
return 0;
}
if (flock(self->fd, LOCK_UN) < 0) {
RETURN_OS_ERROR;
}
}
if (usleep(1000) < 0) {
RETURN_OS_ERROR;
}
Py_END_ALLOW_THREADS
if (PyErr_CheckSignals() < 0) {
return -1;
}
} while (get_now_monotonic() < 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;
}
# define COPY(_type, _field) _type tmp_##_field = MEM(_field)
COPY(unsigned, width);
COPY(unsigned, height);
COPY(unsigned, format);
COPY(unsigned, stride);
COPY(bool, online);
COPY(double, grab_ts);
COPY(double, encode_begin_ts);
COPY(double, encode_end_ts);
COPY(unsigned, used);
# undef COPY
// Временный буффер используется для скорейшего разблокирования синка
if (self->tmp_data_allocated < MEM(used)) {
size_t size = MEM(used) + (512 * 1024);
A_REALLOC(self->tmp_data, size);
self->tmp_data_allocated = size;
}
memcpy(self->tmp_data, MEM(data), MEM(used));
MEM(last_client_ts) = get_now_monotonic();
self->last_id = MEM(id);
if (flock(self->fd, LOCK_UN) < 0) {
return PyErr_SetFromErrno(PyExc_OSError);
}
PyDict_Clear(self->frame);
# define SET_VALUE(_key, _maker) { \
PyObject *_tmp = _maker; \
if (_tmp == NULL) { \
return NULL; \
} \
if (PyDict_SetItemString(self->frame, _key, _tmp) < 0) { \
Py_DECREF(_tmp); \
return NULL; \
} \
Py_DECREF(_tmp); \
}
SET_VALUE("width", PyLong_FromLong(tmp_width));
SET_VALUE("height", PyLong_FromLong(tmp_height));
SET_VALUE("format", PyLong_FromLong(tmp_format));
SET_VALUE("stride", PyLong_FromLong(tmp_stride));
SET_VALUE("online", PyBool_FromLong(tmp_online));
SET_VALUE("grab_ts", PyFloat_FromDouble(tmp_grab_ts));
SET_VALUE("encode_begin_ts", PyFloat_FromDouble(tmp_encode_begin_ts));
SET_VALUE("encode_end_ts", PyFloat_FromDouble(tmp_encode_end_ts));
SET_VALUE("data", PyBytes_FromStringAndSize((const char *)self->tmp_data, tmp_used));
# undef SET_VALUE
Py_INCREF(self->frame);
return self->frame;
}
#undef MEM
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)
#undef FIELD_GETTER
static PyMethodDef MemsinkObject_methods[] = {
# define ADD_METHOD(_meth, _flags) {.ml_name = #_meth, .ml_meth = (PyCFunction)MemsinkObject_##_meth, .ml_flags = (_flags)}
ADD_METHOD(close, METH_NOARGS),
ADD_METHOD(enter, METH_NOARGS),
ADD_METHOD(exit, METH_VARARGS),
ADD_METHOD(wait_frame, METH_NOARGS),
ADD_METHOD(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),
{},
# undef ADD_GETTER
};
static PyTypeObject MemsinkType = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "ustreamer.Memsink",
.tp_basicsize = sizeof(MemsinkObject),
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_new = PyType_GenericNew,
.tp_init = (initproc)MemsinkObject_init,
.tp_dealloc = (destructor)MemsinkObject_dealloc,
.tp_repr = (reprfunc)MemsinkObject_repr,
.tp_methods = MemsinkObject_methods,
.tp_getset = MemsinkObject_getsets,
};
static PyModuleDef ustreamer_Module = {
PyModuleDef_HEAD_INIT,
.m_name = "ustreamer",
.m_size = -1,
};
PyMODINIT_FUNC PyInit_ustreamer(void) { // cppcheck-suppress unusedFunction
PyObject *module = PyModule_Create(&ustreamer_Module);
if (module == NULL) {
return NULL;
}
if (PyType_Ready(&MemsinkType) < 0) {
return NULL;
}
Py_INCREF(&MemsinkType);
if (PyModule_AddObject(module, "Memsink", (PyObject *)&MemsinkType) < 0) {
return NULL;
}
return module;
}

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

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

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

@@ -0,0 +1,49 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 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;
} output_file_s;
output_file_s *output_file_init(const char *path, bool json);
void output_file_write(void *v_output, const frame_s *frame);
void output_file_destroy(void *v_output);

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

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

View File

@@ -1,237 +0,0 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "encoder.h"
#include <stdlib.h>
#include <stdbool.h>
#include <strings.h>
#include <assert.h>
#include <linux/videodev2.h>
#include "tools.h"
#include "threading.h"
#include "logging.h"
#include "device.h"
#include "encoders/cpu/encoder.h"
#include "encoders/hw/encoder.h"
#ifdef WITH_OMX
# include "encoders/omx/encoder.h"
#endif
static const struct {
const char *name;
const enum encoder_type_t type;
} _ENCODER_TYPES[] = {
{"CPU", ENCODER_TYPE_CPU},
{"HW", ENCODER_TYPE_HW},
# ifdef WITH_OMX
{"OMX", ENCODER_TYPE_OMX},
# endif
};
struct encoder_t *encoder_init(void) {
struct encoder_runtime_t *run;
struct encoder_t *encoder;
A_CALLOC(run, 1);
run->type = ENCODER_TYPE_CPU;
run->quality = 80;
A_MUTEX_INIT(&run->mutex);
A_CALLOC(encoder, 1);
encoder->type = run->type;
encoder->quality = run->quality;
encoder->run = run;
return encoder;
}
void encoder_destroy(struct encoder_t *encoder) {
# ifdef WITH_OMX
if (encoder->run->omxs) {
for (unsigned index = 0; index < encoder->run->n_omxs; ++index) {
if (encoder->run->omxs[index]) {
omx_encoder_destroy(encoder->run->omxs[index]);
}
}
free(encoder->run->omxs);
}
# endif
A_MUTEX_DESTROY(&encoder->run->mutex);
free(encoder->run);
free(encoder);
}
enum encoder_type_t encoder_parse_type(const char *str) {
for (unsigned index = 0; index < ARRAY_LEN(_ENCODER_TYPES); ++index) {
if (!strcasecmp(str, _ENCODER_TYPES[index].name)) {
return _ENCODER_TYPES[index].type;
}
}
return ENCODER_TYPE_UNKNOWN;
}
const char *encoder_type_to_string(enum encoder_type_t type) {
for (unsigned index = 0; index < ARRAY_LEN(_ENCODER_TYPES); ++index) {
if (_ENCODER_TYPES[index].type == type) {
return _ENCODER_TYPES[index].name;
}
}
return _ENCODER_TYPES[0].name;
}
void encoder_prepare(struct encoder_t *encoder, struct device_t *dev) {
enum encoder_type_t type = (encoder->run->cpu_forced ? ENCODER_TYPE_CPU : encoder->type);
unsigned quality = encoder->quality;
bool cpu_forced = false;
if ((dev->run->format == V4L2_PIX_FMT_MJPEG || dev->run->format == V4L2_PIX_FMT_JPEG) && type != ENCODER_TYPE_HW) {
LOG_INFO("Switching to HW encoder because the input format is (M)JPEG");
type = ENCODER_TYPE_HW;
}
if (type == ENCODER_TYPE_HW) {
if (dev->run->format != V4L2_PIX_FMT_MJPEG && dev->run->format != V4L2_PIX_FMT_JPEG) {
LOG_INFO("Switching to CPU encoder because the input format is not (M)JPEG");
goto use_cpu;
}
if (hw_encoder_prepare(dev, quality) < 0) {
quality = 0;
}
dev->run->n_workers = 1;
}
# ifdef WITH_OMX
else if (type == ENCODER_TYPE_OMX) {
for (unsigned index = 0; index < encoder->n_glitched_resolutions; ++index) {
if (
encoder->glitched_resolutions[index][0] == dev->run->width
&& encoder->glitched_resolutions[index][1] == dev->run->height
) {
LOG_INFO("Switching to CPU encoder the resolution %ux%u marked as glitchy for OMX",
dev->run->width, dev->run->height);
goto use_cpu;
}
}
LOG_DEBUG("Preparing OMX encoder ...");
if (dev->run->n_workers > OMX_MAX_ENCODERS) {
LOG_INFO("OMX encoder sets limit for worker threads: %u", OMX_MAX_ENCODERS);
dev->run->n_workers = OMX_MAX_ENCODERS;
}
if (encoder->run->omxs == NULL) {
A_CALLOC(encoder->run->omxs, OMX_MAX_ENCODERS);
}
// Начинаем с нуля и доинициализируем на следующих заходах при необходимости
for (; encoder->run->n_omxs < dev->run->n_workers; ++encoder->run->n_omxs) {
if ((encoder->run->omxs[encoder->run->n_omxs] = omx_encoder_init()) == NULL) {
LOG_ERROR("Can't initialize OMX encoder, falling back to CPU");
goto force_cpu;
}
}
for (unsigned index = 0; index < encoder->run->n_omxs; ++index) {
if (omx_encoder_prepare(encoder->run->omxs[index], dev, quality) < 0) {
LOG_ERROR("Can't prepare OMX encoder, falling back to CPU");
goto force_cpu;
}
}
}
# endif
goto ok;
# pragma GCC diagnostic ignored "-Wunused-label"
# pragma GCC diagnostic push
// cppcheck-suppress unusedLabel
force_cpu:
cpu_forced = true;
# pragma GCC diagnostic pop
use_cpu:
type = ENCODER_TYPE_CPU;
quality = encoder->quality;
ok:
if (quality == 0) {
LOG_INFO("Using JPEG quality: encoder default");
} else {
LOG_INFO("Using JPEG quality: %u%%", quality);
}
A_MUTEX_LOCK(&encoder->run->mutex);
encoder->run->type = type;
encoder->run->quality = quality;
if (cpu_forced) {
encoder->run->cpu_forced = true;
}
A_MUTEX_UNLOCK(&encoder->run->mutex);
}
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic push
int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, unsigned worker_number, unsigned buf_index) {
#pragma GCC diagnostic pop
assert(encoder->run->type != ENCODER_TYPE_UNKNOWN);
dev->run->pictures[buf_index]->encode_begin_ts = get_now_monotonic();
if (encoder->run->type == ENCODER_TYPE_CPU) {
cpu_encoder_compress_buffer(dev, buf_index, encoder->run->quality);
} else if (encoder->run->type == ENCODER_TYPE_HW) {
hw_encoder_compress_buffer(dev, buf_index);
}
# ifdef WITH_OMX
else if (encoder->run->type == ENCODER_TYPE_OMX) {
if (omx_encoder_compress_buffer(encoder->run->omxs[worker_number], dev, buf_index) < 0) {
goto error;
}
}
# endif
dev->run->pictures[buf_index]->encode_end_ts = get_now_monotonic();
dev->run->pictures[buf_index]->width = dev->run->width;
dev->run->pictures[buf_index]->height = dev->run->height;
return 0;
# pragma GCC diagnostic ignored "-Wunused-label"
# pragma GCC diagnostic push
// cppcheck-suppress unusedLabel
error:
LOG_INFO("Error while compressing buffer, falling back to CPU");
A_MUTEX_LOCK(&encoder->run->mutex);
encoder->run->cpu_forced = true;
A_MUTEX_UNLOCK(&encoder->run->mutex);
return -1;
# pragma GCC diagnostic pop
}

View File

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

View File

@@ -1,96 +0,0 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <stdlib.h>
#include <wiringPi.h>
#include "tools.h"
#include "logging.h"
int gpio_pin_prog_running;
int gpio_pin_stream_online;
int gpio_pin_has_http_clients;
int gpio_pin_workers_busy_at;
#define GPIO_INIT { \
gpio_pin_prog_running = -1; \
gpio_pin_stream_online = -1; \
gpio_pin_has_http_clients = -1; \
gpio_pin_workers_busy_at = -1; \
}
#define GPIO_INIT_PIN(_role, _offset) _gpio_init_pin(#_role, gpio_pin_##_role, _offset)
INLINE void _gpio_init_pin(const char *role, int base, unsigned offset) {
if (base >= 0) {
pinMode(base + offset, OUTPUT);
if (offset == 0) {
LOG_INFO("GPIO: Using pin %d as %s", base, role);
} else {
LOG_INFO("GPIO: Using pin %d+%u as %s", base, offset, role);
}
}
}
#define GPIO_INIT_PINOUT { \
if ( \
gpio_pin_prog_running >= 0 \
|| gpio_pin_stream_online >= 0 \
|| gpio_pin_has_http_clients >= 0 \
|| gpio_pin_workers_busy_at >= 0 \
) { \
LOG_INFO("GPIO: Using wiringPi"); \
if (wiringPiSetupGpio() < 0) { \
LOG_PERROR("GPIO: Can't initialize wiringPi"); \
exit(1); \
} else { \
GPIO_INIT_PIN(prog_running, 0); \
GPIO_INIT_PIN(stream_online, 0); \
GPIO_INIT_PIN(has_http_clients, 0); \
GPIO_INIT_PIN(workers_busy_at, 0); \
} \
} \
}
#define GPIO_SET_STATE(_role, _offset, _state) _gpio_set_state(#_role, gpio_pin_##_role, _offset, _state)
INLINE void _gpio_set_state(const char *role, int base, unsigned offset, int state) {
if (base >= 0) {
if (offset == 0) {
LOG_DEBUG("GPIO: Writing %d to pin %d (%s)", state, base, role);
} else {
LOG_DEBUG("GPIO: Writing %d to pin %d+%u (%s)", state, base, offset, role);
}
digitalWrite(base + offset, state);
}
}
#define GPIO_SET_LOW(_role) GPIO_SET_STATE(_role, 0, LOW)
#define GPIO_SET_HIGH(_role) GPIO_SET_STATE(_role, 0, HIGH)
#define GPIO_SET_LOW_AT(_role, _offset) GPIO_SET_STATE(_role, _offset, LOW)
#define GPIO_SET_HIGH_AT(_role, _offset) GPIO_SET_STATE(_role, _offset, HIGH)

View File

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

View File

@@ -1,118 +0,0 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <stdbool.h>
#include <sys/stat.h>
#include <event2/event.h>
#include <event2/http.h>
#include <event2/util.h>
#include "../picture.h"
#include "../stream.h"
struct stream_client_t {
struct http_server_t *server;
struct evhttp_request *request;
char *key;
bool extra_headers;
bool advance_headers;
bool dual_final_frames;
char id[37]; // ex. "1b4e28ba-2fa1-11d2-883f-0016d3cca427" + "\0"
bool need_initial;
bool need_first_frame;
bool updated_prev;
unsigned fps;
unsigned fps_accum;
long long fps_accum_second;
struct stream_client_t *prev;
struct stream_client_t *next;
};
struct exposed_t {
struct picture_t *picture;
unsigned captured_fps;
unsigned queued_fps;
bool online;
unsigned dropped;
long double expose_begin_ts;
long double expose_cmp_ts;
long double expose_end_ts;
long double last_as_blank_ts;
bool notify_last_online;
unsigned notify_last_width;
unsigned notify_last_height;
};
struct http_server_runtime_t {
struct event_base *base;
struct evhttp *http;
evutil_socket_t unix_fd;
char *auth_token;
struct event *refresh;
struct stream_t *stream;
struct exposed_t *exposed;
struct stream_client_t *stream_clients;
unsigned stream_clients_count;
struct picture_t *blank;
unsigned drop_same_frames_blank;
};
struct http_server_t {
char *host;
unsigned port;
char *unix_path;
bool unix_rm;
mode_t unix_mode;
unsigned timeout;
char *user;
char *passwd;
char *static_path;
char *blank_path;
int last_as_blank;
unsigned drop_same_frames;
bool slowdown;
unsigned fake_width;
unsigned fake_height;
bool notify_parent;
struct http_server_runtime_t *run;
};
struct http_server_t *http_server_init(struct stream_t *stream);
void http_server_destroy(struct http_server_t *server);
int http_server_listen(struct http_server_t *server);
void http_server_loop(struct http_server_t *server);
void http_server_loop_break(struct http_server_t *server);

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 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,6 @@
#include "base64.h"
#include <stdlib.h>
#include <string.h>
#include "../tools.h"
static const char _ENCODING_TABLE[] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
@@ -42,30 +37,36 @@ static const char _ENCODING_TABLE[] = {
static const unsigned _MOD_TABLE[] = {0, 2, 1};
char *base64_encode(const unsigned char *str) {
size_t str_len = strlen((const char *)str);
size_t encoded_size = 4 * ((str_len + 2) / 3) + 1; // +1 for '\0'
char *encoded;
void base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *allocated) {
const size_t encoded_size = 4 * ((size + 2) / 3) + 1; // +1 for '\0'
A_CALLOC(encoded, encoded_size);
if (*encoded == NULL || (allocated && *allocated < encoded_size)) {
A_REALLOC(*encoded, encoded_size);
if (allocated) {
*allocated = encoded_size;
}
}
for (unsigned str_index = 0, encoded_index = 0; str_index < str_len;) {
unsigned octet_a = (str_index < str_len ? (unsigned char)str[str_index++] : 0);
unsigned octet_b = (str_index < str_len ? (unsigned char)str[str_index++] : 0);
unsigned octet_c = (str_index < str_len ? (unsigned char)str[str_index++] : 0);
for (unsigned data_index = 0, encoded_index = 0; data_index < size;) {
# define OCTET(_name) unsigned _name = (data_index < size ? (uint8_t)data[data_index++] : 0)
OCTET(octet_a);
OCTET(octet_b);
OCTET(octet_c);
# undef OCTET
unsigned triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
encoded[encoded_index++] = _ENCODING_TABLE[(triple >> 3 * 6) & 0x3F];
encoded[encoded_index++] = _ENCODING_TABLE[(triple >> 2 * 6) & 0x3F];
encoded[encoded_index++] = _ENCODING_TABLE[(triple >> 1 * 6) & 0x3F];
encoded[encoded_index++] = _ENCODING_TABLE[(triple >> 0 * 6) & 0x3F];
# define ENCODE(_offset) (*encoded)[encoded_index++] = _ENCODING_TABLE[(triple >> _offset * 6) & 0x3F]
ENCODE(3);
ENCODE(2);
ENCODE(1);
ENCODE(0);
# undef ENCODE
}
for (unsigned index = 0; index < _MOD_TABLE[str_len % 3]; index++) {
encoded[encoded_size - 2 - index] = '=';
for (unsigned index = 0; index < _MOD_TABLE[size % 3]; index++) {
(*encoded)[encoded_size - 2 - index] = '=';
}
encoded[encoded_size - 1] = '\0';
return encoded;
(*encoded)[encoded_size - 1] = '\0';
}

View File

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

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 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,5 +23,5 @@
#pragma once
#ifndef VERSION
# define VERSION "1.19"
# define VERSION "3.8"
#endif

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

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

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

@@ -0,0 +1,80 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <linux/videodev2.h>
#include "tools.h"
#include "logging.h"
typedef struct {
const char *name;
uint8_t *data;
size_t used;
size_t allocated;
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;
long double grab_ts;
long double encode_begin_ts;
long double encode_end_ts;
bool managed;
} frame_s;
frame_s *frame_init(const char *name);
void frame_destroy(frame_s *frame);
void frame_realloc_data(frame_s *frame, size_t size);
void frame_set_data(frame_s *frame, const uint8_t *data, size_t size);
void frame_append_data(frame_s *frame, const uint8_t *data, size_t size);
void frame_copy(const frame_s *src, frame_s *dest);
void frame_copy_meta(const frame_s *src, frame_s *dest);
bool frame_compare(const frame_s *a, const frame_s *b);
unsigned frame_get_padding(const frame_s *frame);
const char *fourcc_to_string(unsigned format, char *buf, size_t size);
inline bool is_jpeg(unsigned format) {
return (format == V4L2_PIX_FMT_JPEG || format == V4L2_PIX_FMT_MJPEG);
}

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -20,10 +20,6 @@
*****************************************************************************/
#include <stdbool.h>
#include <pthread.h>
#include "logging.h"

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 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 #
@@ -54,7 +54,7 @@ extern pthread_mutex_t log_mutex;
#define LOGGING_INIT { \
log_level = LOG_LEVEL_INFO; \
log_colored = isatty(1); \
log_colored = isatty(2); \
A_MUTEX_INIT(&log_mutex); \
}
@@ -76,10 +76,10 @@ extern pthread_mutex_t log_mutex;
#define SEP_INFO(_ch) { \
LOGGING_LOCK; \
for (int _i = 0; _i < 80; ++_i) { \
putchar(_ch); \
fputc(_ch, stderr); \
} \
putchar('\n'); \
fflush(stdout); \
fputc('\n', stderr); \
fflush(stderr); \
LOGGING_UNLOCK; \
}
@@ -94,14 +94,15 @@ extern pthread_mutex_t log_mutex;
char _tname_buf[MAX_THREAD_NAME] = {0}; \
thread_get_name(_tname_buf); \
if (log_colored) { \
printf(COLOR_GRAY "-- " _label_color _label COLOR_GRAY " [%.03Lf %9s]" " -- " COLOR_RESET _msg_color _msg COLOR_RESET, \
fprintf(stderr, COLOR_GRAY "-- " _label_color _label COLOR_GRAY \
" [%.03Lf %9s]" " -- " COLOR_RESET _msg_color _msg COLOR_RESET, \
get_now_monotonic(), _tname_buf, ##__VA_ARGS__); \
} else { \
printf("-- " _label " [%.03Lf %9s] -- " _msg, \
fprintf(stderr, "-- " _label " [%.03Lf %9s] -- " _msg, \
get_now_monotonic(), _tname_buf, ##__VA_ARGS__); \
} \
putchar('\n'); \
fflush(stdout); \
fputc('\n', stderr); \
fflush(stderr); \
}
#define LOG_PRINTF(_label_color, _label, _msg_color, _msg, ...) { \
@@ -149,7 +150,7 @@ extern pthread_mutex_t log_mutex;
#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); \
char *_perror_ptr = errno_to_string(errno, _perror_buf, 1023); \
LOG_PRINTF(COLOR_BLUE, "VERB ", COLOR_BLUE, _msg ": %s", ##__VA_ARGS__, _perror_ptr); \
} \
}

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

@@ -0,0 +1,214 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "memsink.h"
memsink_s *memsink_init(
const char *name, const char *obj, bool server,
mode_t mode, bool rm, unsigned client_ttl, unsigned timeout) {
memsink_s *sink;
A_CALLOC(sink, 1);
sink->name = name;
sink->obj = obj;
sink->server = server;
sink->rm = rm;
sink->client_ttl = client_ttl;
sink->timeout = timeout;
sink->fd = -1;
sink->mem = MAP_FAILED;
LOG_INFO("Using %s-sink: %s", name, obj);
mode_t mask = umask(0);
sink->fd = shm_open(sink->obj, (server ? O_RDWR | O_CREAT : O_RDWR), mode);
umask(mask);
if (sink->fd == -1) {
umask(mask);
LOG_PERROR("%s-sink: Can't open shared memory", name);
goto error;
}
if (sink->server && ftruncate(sink->fd, sizeof(memsink_shared_s)) < 0) {
LOG_PERROR("%s-sink: Can't truncate shared memory", name);
goto error;
}
if ((sink->mem = 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);
goto error;
}
return sink;
error:
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);
}
}
if (sink->fd >= 0) {
if (close(sink->fd) < 0) {
LOG_PERROR("%s-sink: Can't close shared memory fd", sink->name);
}
if (sink->rm && shm_unlink(sink->obj) < 0) {
if (errno != ENOENT) {
LOG_PERROR("%s-sink: Can't remove shared memory", sink->name);
}
}
}
free(sink);
}
bool memsink_server_check(memsink_s *sink, const frame_s *frame) {
// Возвращает true, если если клиенты ИЛИ изменились метаданные
assert(sink->server);
if (flock(sink->fd, LOCK_EX | LOCK_NB) < 0) {
if (errno == EWOULDBLOCK) {
sink->has_clients = true;
return true;
}
LOG_PERROR("%s-sink: Can't lock memory", sink->name);
return false;
}
sink->has_clients = (sink->mem->last_client_ts + sink->client_ttl > get_now_monotonic());
# define NEQ(_field) (sink->mem->_field != frame->_field)
bool retval = (sink->has_clients || NEQ(width) || NEQ(height) || NEQ(format) || NEQ(stride) || NEQ(online));
# undef NEQ
if (flock(sink->fd, LOCK_UN) < 0) {
LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
return false;
}
return retval;
}
int memsink_server_put(memsink_s *sink, const frame_s *frame) {
assert(sink->server);
const long double now = get_now_monotonic();
if (frame->used > MEMSINK_MAX_DATA) {
LOG_ERROR("%s-sink: Can't put frame: is too big (%zu > %zu)",
sink->name, frame->used, MEMSINK_MAX_DATA);
return 0; // -2
}
if (flock_timedwait_monotonic(sink->fd, 1) == 0) {
LOG_VERBOSE("%s-sink: >>>>> Exposing new frame ...", sink->name);
# define COPY(_field) sink->mem->_field = frame->_field
sink->last_id = 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_client_ts + sink->client_ttl > get_now_monotonic());
memcpy(sink->mem->data, frame->data, frame->used);
sink->mem->magic = MEMSINK_MAGIC;
sink->mem->version = MEMSINK_VERSION;
# undef COPY
if (flock(sink->fd, LOCK_UN) < 0) {
LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
return -1;
}
LOG_VERBOSE("%s-sink: Exposed new frame; full exposition time = %.3Lf",
sink->name, get_now_monotonic() - now);
} else if (errno == EWOULDBLOCK) {
LOG_VERBOSE("%s-sink: ===== Shared memory is busy now; frame skipped", sink->name);
} else {
LOG_PERROR("%s-sink: Can't lock memory", sink->name);
return -1;
}
return 0;
}
int memsink_client_get(memsink_s *sink, frame_s *frame) { // cppcheck-suppress unusedFunction
assert(!sink->server); // Client only
if (flock_timedwait_monotonic(sink->fd, sink->timeout) < 0) {
if (errno == EWOULDBLOCK) {
return -2;
}
LOG_PERROR("%s-sink: Can't lock memory", sink->name);
return -1;
}
int retval = -2; // Not updated
if (sink->mem->magic == MEMSINK_MAGIC) {
if (sink->mem->version != MEMSINK_VERSION) {
LOG_ERROR("%s-sink: Protocol version mismatch: sink=%u, required=%u",
sink->name, sink->mem->version, MEMSINK_VERSION);
retval = -1;
goto done;
}
if (sink->mem->id != sink->last_id) { // When updated
# 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);
frame_set_data(frame, sink->mem->data, sink->mem->used);
# undef COPY
retval = 0;
}
sink->mem->last_client_ts = get_now_monotonic();
}
done:
if (flock(sink->fd, LOCK_UN) < 0) {
LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
return -1;
}
return retval;
}

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

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

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

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

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

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

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -22,8 +22,12 @@
#pragma once
#include "../../device.h"
#include <string.h>
#include <ctype.h>
#include <getopt.h>
#include <assert.h>
#include <sys/types.h>
int hw_encoder_prepare(struct device_t *dev, unsigned quality);
void hw_encoder_compress_buffer(struct device_t *dev, unsigned index);
void build_short_options(const struct option opts[], char *short_opts, size_t size);

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 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 #
@@ -113,7 +113,7 @@ INLINE void process_set_name_prefix(int argc, char *argv[], const char *prefix)
size_t arg_len = strlen(argv[index]);
if (used + arg_len + 16 >= allocated) {
allocated += arg_len + 2048;
A_REALLOC(cmdline, allocated);
A_REALLOC(cmdline, allocated); // cppcheck-suppress memleakOnRealloc // False-positive (ok with assert)
}
strcat(cmdline, " ");

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 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 #
@@ -102,7 +102,23 @@ INLINE void thread_get_name(char *name) { // Always required for logging
# endif
if (retval < 0) {
#endif
assert(snprintf(name, MAX_THREAD_NAME, "tid=%d", (pid_t)syscall(SYS_gettid)) > 0);
#if defined(__linux__)
pid_t tid = syscall(SYS_gettid);
#elif defined(__FreeBSD__)
pid_t tid = syscall(SYS_thr_self);
#elif defined(__OpenBSD__)
pid_t tid = syscall(SYS_getthrid);
#elif defined(__NetBSD__)
pid_t tid = syscall(SYS__lwp_self);
#elif defined(__DragonFly__)
pid_t tid = syscall(SYS_lwp_gettid);
#else
pid_t tid = 0; // Makes cppcheck happy
# warning gettid() not implemented
#endif
assert(snprintf(name, MAX_THREAD_NAME, "tid=%d", tid) > 0);
#ifdef WITH_PTHREAD_NP
}
#endif

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 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,13 +23,17 @@
#pragma once
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <errno.h>
#include <math.h>
#include <time.h>
#include <assert.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)))))
@@ -61,12 +65,23 @@ INLINE long long floor_ms(long double now) {
return (long long)now - (now < (long long)now); // floor()
}
INLINE void get_now(clockid_t clk_id, time_t *sec, long *msec) {
struct timespec spec;
INLINE uint32_t triple_u32(uint32_t x) {
// https://nullprogram.com/blog/2018/07/31/
x ^= x >> 17;
x *= UINT32_C(0xED5AD4BB);
x ^= x >> 11;
x *= UINT32_C(0xAC4C1B51);
x ^= x >> 15;
x *= UINT32_C(0x31848BAB);
x ^= x >> 14;
return x;
}
assert(!clock_gettime(clk_id, &spec));
*sec = spec.tv_sec;
*msec = round(spec.tv_nsec / 1.0e6);
INLINE void get_now(clockid_t clk_id, time_t *sec, long *msec) {
struct timespec ts;
assert(!clock_gettime(clk_id, &ts));
*sec = ts.tv_sec;
*msec = round(ts.tv_nsec / 1.0e6);
if (*msec > 999) {
*sec += 1;
@@ -74,18 +89,55 @@ 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
#elif defined(CLOCK_MONOTONIC_FAST)
# define X_CLOCK_MONOTONIC CLOCK_MONOTONIC_FAST
#else
# define X_CLOCK_MONOTONIC CLOCK_MONOTONIC
#endif
INLINE long double get_now_monotonic(void) {
time_t sec;
long msec;
get_now(CLOCK_MONOTONIC_RAW, &sec, &msec);
get_now(X_CLOCK_MONOTONIC, &sec, &msec);
return (long double)sec + ((long double)msec) / 1000;
}
INLINE uint64_t get_now_id(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);
}
#undef X_CLOCK_MONOTONIC
INLINE long double get_now_real(void) {
time_t sec;
long msec;
get_now(CLOCK_REALTIME, &sec, &msec);
return (long double)sec + ((long double)msec) / 1000;
}
INLINE unsigned get_cores_available(void) {
long cores_sysconf = sysconf(_SC_NPROCESSORS_ONLN);
cores_sysconf = (cores_sysconf < 0 ? 0 : cores_sysconf);
return max_u(min_u(cores_sysconf, 4), 1);
}
INLINE int flock_timedwait_monotonic(int fd, long double timeout) {
long double deadline_ts = get_now_monotonic() + timeout;
int retval = -1;
while (true) {
retval = flock(fd, LOCK_EX | LOCK_NB);
if (retval == 0 || errno != EWOULDBLOCK || get_now_monotonic() > deadline_ts) {
break;
}
if (usleep(1000) < 0) {
break;
}
}
return retval;
}

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

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

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 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,19 +22,19 @@
#pragma once
#include "device.h"
#include "encoder.h"
#include "http/server.h"
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <setjmp.h>
#include <assert.h>
#include <sys/types.h>
#include <jpeglib.h>
#include <linux/videodev2.h>
#include "logging.h"
#include "frame.h"
struct options_t {
int argc;
char **argv;
char **argv_copy;
};
struct options_t *options_init(int argc, char *argv[]);
void options_destroy(struct options_t *options);
int options_parse(struct options_t *options, struct device_t *dev, struct encoder_t *encoder, struct http_server_t *server);
int unjpeg(const frame_s *src, frame_s *dest, bool decode);

View File

@@ -1,664 +0,0 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "options.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>
#include <limits.h>
#include <getopt.h>
#include <errno.h>
#include <assert.h>
#include "config.h"
#include "logging.h"
#include "process.h"
#include "device.h"
#include "encoder.h"
#include "http/server.h"
#ifdef WITH_GPIO
# include "gpio.h"
#endif
enum _OPT_VALUES {
_O_DEVICE = 'd',
_O_INPUT = 'i',
_O_RESOLUTION = 'r',
_O_FORMAT = 'm',
_O_TV_STANDARD = 'a',
_O_IO_METHOD = 'I',
_O_DESIRED_FPS = 'f',
_O_MIN_FRAME_SIZE = 'z',
_O_PERSISTENT = 'n',
_O_DV_TIMINGS = 't',
_O_BUFFERS = 'b',
_O_WORKERS = 'w',
_O_QUALITY = 'q',
_O_ENCODER = 'c',
#ifdef WITH_OMX
_O_GLITCHED_RESOLUTIONS = 'g',
#endif
_O_HOST = 's',
_O_PORT = 'p',
_O_UNIX = 'U',
_O_UNIX_RM = 'D',
_O_UNIX_MODE = 'M',
_O_BLANK = 'k',
_O_LAST_AS_BLANK = 'K',
_O_DROP_SAME_FRAMES = 'e',
_O_SLOWDOWN = 'l',
_O_FAKE_RESOLUTION = 'R',
_O_HELP = 'h',
_O_VERSION = 'v',
// Longs only
_O_DEVICE_TIMEOUT = 10000,
_O_DEVICE_ERROR_DELAY,
_O_IMAGE_DEFAULT,
_O_BRIGHTNESS,
_O_CONTRAST,
_O_SATURATION,
_O_HUE,
_O_GAMMA,
_O_SHARPNESS,
_O_BACKLIGHT_COMPENSATION,
_O_WHITE_BALANCE,
_O_GAIN,
_O_USER,
_O_PASSWD,
_O_STATIC,
_O_SERVER_TIMEOUT,
#ifdef WITH_GPIO
_O_GPIO_PROG_RUNNING,
_O_GPIO_STREAM_ONLINE,
_O_GPIO_HAS_HTTP_CLIENTS,
_O_GPIO_WORKERS_BUSY_AT,
#endif
#ifdef HAS_PDEATHSIG
_O_EXIT_ON_PARENT_DEATH,
#endif
#ifdef WITH_SETPROCTITLE
_O_PROCESS_NAME_PREFIX,
#endif
_O_NOTIFY_PARENT,
_O_LOG_LEVEL,
_O_PERF,
_O_VERBOSE,
_O_DEBUG,
_O_FORCE_LOG_COLORS,
_O_NO_LOG_COLORS,
_O_FEATURES,
};
static const struct option _LONG_OPTS[] = {
{"device", required_argument, NULL, _O_DEVICE},
{"input", required_argument, NULL, _O_INPUT},
{"resolution", required_argument, NULL, _O_RESOLUTION},
{"format", required_argument, NULL, _O_FORMAT},
{"tv-standard", required_argument, NULL, _O_TV_STANDARD},
{"io-method", required_argument, NULL, _O_IO_METHOD},
{"desired-fps", required_argument, NULL, _O_DESIRED_FPS},
{"min-frame-size", required_argument, NULL, _O_MIN_FRAME_SIZE},
{"persistent", no_argument, NULL, _O_PERSISTENT},
{"dv-timings", no_argument, NULL, _O_DV_TIMINGS},
{"buffers", required_argument, NULL, _O_BUFFERS},
{"workers", required_argument, NULL, _O_WORKERS},
{"quality", required_argument, NULL, _O_QUALITY},
{"encoder", required_argument, NULL, _O_ENCODER},
# ifdef WITH_OMX
{"glitched-resolutions", required_argument, NULL, _O_GLITCHED_RESOLUTIONS},
# endif
{"device-timeout", required_argument, NULL, _O_DEVICE_TIMEOUT},
{"device-error-delay", required_argument, NULL, _O_DEVICE_ERROR_DELAY},
{"image-default", no_argument, NULL, _O_IMAGE_DEFAULT},
{"brightness", required_argument, NULL, _O_BRIGHTNESS},
{"contrast", required_argument, NULL, _O_CONTRAST},
{"saturation", required_argument, NULL, _O_SATURATION},
{"hue", required_argument, NULL, _O_HUE},
{"gamma", required_argument, NULL, _O_GAMMA},
{"sharpness", required_argument, NULL, _O_SHARPNESS},
{"backlight-compensation", required_argument, NULL, _O_BACKLIGHT_COMPENSATION},
{"white-balance", required_argument, NULL, _O_WHITE_BALANCE},
{"gain", required_argument, NULL, _O_GAIN},
{"host", required_argument, NULL, _O_HOST},
{"port", required_argument, NULL, _O_PORT},
{"unix", required_argument, NULL, _O_UNIX},
{"unix-rm", no_argument, NULL, _O_UNIX_RM},
{"unix-mode", required_argument, NULL, _O_UNIX_MODE},
{"user", required_argument, NULL, _O_USER},
{"passwd", required_argument, NULL, _O_PASSWD},
{"static", required_argument, NULL, _O_STATIC},
{"blank", required_argument, NULL, _O_BLANK},
{"last-as-blank", required_argument, NULL, _O_LAST_AS_BLANK},
{"drop-same-frames", required_argument, NULL, _O_DROP_SAME_FRAMES},
{"slowdown", no_argument, NULL, _O_SLOWDOWN},
{"fake-resolution", required_argument, NULL, _O_FAKE_RESOLUTION},
{"server-timeout", required_argument, NULL, _O_SERVER_TIMEOUT},
#ifdef WITH_GPIO
{"gpio-prog-running", required_argument, NULL, _O_GPIO_PROG_RUNNING},
{"gpio-stream-online", required_argument, NULL, _O_GPIO_STREAM_ONLINE},
{"gpio-has-http-clients", required_argument, NULL, _O_GPIO_HAS_HTTP_CLIENTS},
{"gpio-workers-busy-at", required_argument, NULL, _O_GPIO_WORKERS_BUSY_AT},
#endif
#ifdef HAS_PDEATHSIG
{"exit-on-parent-death", no_argument, NULL, _O_EXIT_ON_PARENT_DEATH},
#endif
#ifdef WITH_SETPROCTITLE
{"process-name-prefix", required_argument, NULL, _O_PROCESS_NAME_PREFIX},
#endif
{"notify-parent", no_argument, NULL, _O_NOTIFY_PARENT},
{"log-level", required_argument, NULL, _O_LOG_LEVEL},
{"perf", no_argument, NULL, _O_PERF},
{"verbose", no_argument, NULL, _O_VERBOSE},
{"debug", no_argument, NULL, _O_DEBUG},
{"force-log-colors", no_argument, NULL, _O_FORCE_LOG_COLORS},
{"no-log-colors", no_argument, NULL, _O_NO_LOG_COLORS},
{"help", no_argument, NULL, _O_HELP},
{"version", no_argument, NULL, _O_VERSION},
{"features", no_argument, NULL, _O_FEATURES},
{NULL, 0, NULL, 0},
};
static int _parse_resolution(const char *str, unsigned *width, unsigned *height, bool limited);
#ifdef WITH_OMX
static int _parse_glitched_resolutions(const char *str, struct encoder_t *encoder);
#endif
static void _features(void);
static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_server_t *server);
struct options_t *options_init(int argc, char *argv[]) {
struct options_t *options;
A_CALLOC(options, 1);
options->argc = argc;
options->argv = argv;
A_CALLOC(options->argv_copy, argc);
for (int index = 0; index < argc; ++index) {
assert(options->argv_copy[index] = strdup(argv[index]));
}
return options;
}
void options_destroy(struct options_t *options) {
for (int index = 0; index < options->argc; ++index) {
free(options->argv_copy[index]);
}
free(options->argv_copy);
free(options);
}
int options_parse(struct options_t *options, struct device_t *dev, struct encoder_t *encoder, struct http_server_t *server) {
# define OPT_SET(_dest, _value) { \
_dest = _value; \
break; \
}
# define OPT_NUMBER(_name, _dest, _min, _max, _base) { \
errno = 0; char *_end = NULL; long long _tmp = strtoll(optarg, &_end, _base); \
if (errno || *_end || _tmp < _min || _tmp > _max) { \
printf("Invalid value for '%s=%s': min=%lld, max=%lld\n", _name, optarg, (long long)_min, (long long)_max); \
return -1; \
} \
_dest = _tmp; \
break; \
}
# define OPT_RESOLUTION(_name, _dest_width, _dest_height, _limited) { \
switch (_parse_resolution(optarg, &_dest_width, &_dest_height, _limited)) { \
case -1: \
printf("Invalid resolution format for '%s=%s'\n", _name, optarg); \
return -1; \
case -2: \
printf("Invalid width of '%s=%s': min=%u, max=%u\n", _name, optarg, VIDEO_MIN_WIDTH, VIDEO_MAX_WIDTH); \
return -1; \
case -3: \
printf("Invalid height of '%s=%s': min=%u, max=%u\n", _name, optarg, VIDEO_MIN_HEIGHT, VIDEO_MAX_HEIGHT); \
return -1; \
case 0: break; \
default: assert(0 && "Unknown error"); \
} \
break; \
}
# define OPT_PARSE(_name, _dest, _func, _invalid, _available) { \
if ((_dest = _func(optarg)) == _invalid) { \
printf("Unknown " _name ": %s; available: %s\n", optarg, _available); \
return -1; \
} \
break; \
}
# define OPT_CTL_DEFAULT_NOBREAK(_dest) { \
dev->ctl._dest.mode = CTL_MODE_DEFAULT; \
}
# define OPT_CTL_MANUAL(_dest) { \
if (!strcasecmp(optarg, "default")) { \
OPT_CTL_DEFAULT_NOBREAK(_dest); \
} else { \
dev->ctl._dest.mode = CTL_MODE_VALUE; \
OPT_NUMBER("--"#_dest, dev->ctl._dest.value, INT_MIN, INT_MAX, 0); \
} \
break; \
}
# define OPT_CTL_AUTO(_dest) { \
if (!strcasecmp(optarg, "default")) { \
OPT_CTL_DEFAULT_NOBREAK(_dest); \
} else if (!strcasecmp(optarg, "auto")) { \
dev->ctl._dest.mode = CTL_MODE_AUTO; \
} else { \
dev->ctl._dest.mode = CTL_MODE_VALUE; \
OPT_NUMBER("--"#_dest, dev->ctl._dest.value, INT_MIN, INT_MAX, 0); \
} \
break; \
}
int ch;
int short_index;
int opt_index;
char short_opts[1024] = {0};
# ifdef WITH_SETPROCTITLE
char *process_name_prefix = NULL;
# endif
for (short_index = 0, opt_index = 0; _LONG_OPTS[opt_index].name != NULL; ++opt_index) {
if (isalpha(_LONG_OPTS[opt_index].val)) {
short_opts[short_index] = _LONG_OPTS[opt_index].val;
++short_index;
if (_LONG_OPTS[opt_index].has_arg == required_argument) {
short_opts[short_index] = ':';
++short_index;
}
}
}
while ((ch = getopt_long(options->argc, options->argv_copy, short_opts, _LONG_OPTS, NULL)) >= 0) {
switch (ch) {
case _O_DEVICE: OPT_SET(dev->path, optarg);
case _O_INPUT: OPT_NUMBER("--input", dev->input, 0, 128, 0);
case _O_RESOLUTION: OPT_RESOLUTION("--resolution", dev->width, dev->height, true);
# pragma GCC diagnostic ignored "-Wsign-compare"
# pragma GCC diagnostic push
case _O_FORMAT: OPT_PARSE("pixel format", dev->format, device_parse_format, FORMAT_UNKNOWN, FORMATS_STR);
# pragma GCC diagnostic pop
case _O_TV_STANDARD: OPT_PARSE("TV standard", dev->standard, device_parse_standard, STANDARD_UNKNOWN, STANDARDS_STR);
case _O_IO_METHOD: OPT_PARSE("IO method", dev->io_method, device_parse_io_method, IO_METHOD_UNKNOWN, IO_METHODS_STR);
case _O_DESIRED_FPS: OPT_NUMBER("--desired-fps", dev->desired_fps, 0, VIDEO_MAX_FPS, 0);
case _O_MIN_FRAME_SIZE: OPT_NUMBER("--min-frame-size", dev->min_frame_size, 0, 8192, 0);
case _O_PERSISTENT: OPT_SET(dev->persistent, true);
case _O_DV_TIMINGS: OPT_SET(dev->dv_timings, true);
case _O_BUFFERS: OPT_NUMBER("--buffers", dev->n_buffers, 1, 32, 0);
case _O_WORKERS: OPT_NUMBER("--workers", dev->n_workers, 1, 32, 0);
case _O_QUALITY: OPT_NUMBER("--quality", encoder->quality, 1, 100, 0);
case _O_ENCODER: OPT_PARSE("encoder type", encoder->type, encoder_parse_type, ENCODER_TYPE_UNKNOWN, ENCODER_TYPES_STR);
# ifdef WITH_OMX
case _O_GLITCHED_RESOLUTIONS:
if (_parse_glitched_resolutions(optarg, encoder) < 0) {
return -1;
}
break;
# endif
case _O_DEVICE_TIMEOUT: OPT_NUMBER("--device-timeout", dev->timeout, 1, 60, 0);
case _O_DEVICE_ERROR_DELAY: OPT_NUMBER("--device-error-delay", dev->error_delay, 1, 60, 0);
case _O_IMAGE_DEFAULT:
OPT_CTL_DEFAULT_NOBREAK(brightness);
OPT_CTL_DEFAULT_NOBREAK(contrast);
OPT_CTL_DEFAULT_NOBREAK(saturation);
OPT_CTL_DEFAULT_NOBREAK(hue);
OPT_CTL_DEFAULT_NOBREAK(gamma);
OPT_CTL_DEFAULT_NOBREAK(sharpness);
OPT_CTL_DEFAULT_NOBREAK(backlight_compensation);
OPT_CTL_DEFAULT_NOBREAK(white_balance);
OPT_CTL_DEFAULT_NOBREAK(gain);
break;
case _O_BRIGHTNESS: OPT_CTL_AUTO(brightness);
case _O_CONTRAST: OPT_CTL_MANUAL(contrast);
case _O_SATURATION: OPT_CTL_MANUAL(saturation);
case _O_HUE: OPT_CTL_AUTO(hue);
case _O_GAMMA: OPT_CTL_MANUAL(gamma);
case _O_SHARPNESS: OPT_CTL_MANUAL(sharpness);
case _O_BACKLIGHT_COMPENSATION: OPT_CTL_MANUAL(backlight_compensation);
case _O_WHITE_BALANCE: OPT_CTL_AUTO(white_balance);
case _O_GAIN: OPT_CTL_AUTO(gain);
case _O_HOST: OPT_SET(server->host, optarg);
case _O_PORT: OPT_NUMBER("--port", server->port, 1, 65535, 0);
case _O_UNIX: OPT_SET(server->unix_path, optarg);
case _O_UNIX_RM: OPT_SET(server->unix_rm, true);
case _O_UNIX_MODE: OPT_NUMBER("--unix-mode", server->unix_mode, INT_MIN, INT_MAX, 8);
case _O_USER: OPT_SET(server->user, optarg);
case _O_PASSWD: OPT_SET(server->passwd, optarg);
case _O_STATIC: OPT_SET(server->static_path, optarg);
case _O_BLANK: OPT_SET(server->blank_path, optarg);
case _O_LAST_AS_BLANK: OPT_NUMBER("--last-as-blank", server->last_as_blank, 0, 86400, 0);
case _O_DROP_SAME_FRAMES: OPT_NUMBER("--drop-same-frames", server->drop_same_frames, 0, VIDEO_MAX_FPS, 0);
case _O_SLOWDOWN: OPT_SET(server->slowdown, true);
case _O_FAKE_RESOLUTION: OPT_RESOLUTION("--fake-resolution", server->fake_width, server->fake_height, false);
case _O_SERVER_TIMEOUT: OPT_NUMBER("--server-timeout", server->timeout, 1, 60, 0);
# ifdef WITH_GPIO
case _O_GPIO_PROG_RUNNING: OPT_NUMBER("--gpio-prog-running", gpio_pin_prog_running, 0, 256, 0);
case _O_GPIO_STREAM_ONLINE: OPT_NUMBER("--gpio-stream-online", gpio_pin_stream_online, 0, 256, 0);
case _O_GPIO_HAS_HTTP_CLIENTS: OPT_NUMBER("--gpio-has-http-clients", gpio_pin_has_http_clients, 0, 256, 0);
case _O_GPIO_WORKERS_BUSY_AT: OPT_NUMBER("--gpio-workers-busy-at", gpio_pin_workers_busy_at, 0, 256, 0);
# endif
# ifdef HAS_PDEATHSIG
case _O_EXIT_ON_PARENT_DEATH:
if (process_track_parent_death() < 0) {
return -1;
};
break;
# endif
# ifdef WITH_SETPROCTITLE
case _O_PROCESS_NAME_PREFIX: OPT_SET(process_name_prefix, optarg);
# endif
case _O_NOTIFY_PARENT: OPT_SET(server->notify_parent, true);
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_HELP: _help(dev, encoder, server); return 1;
case _O_VERSION: puts(VERSION); return 1;
case _O_FEATURES: _features(); return 1;
case 0: break;
default: _help(dev, encoder, server); return -1;
}
}
# ifdef WITH_SETPROCTITLE
if (process_name_prefix != NULL) {
process_set_name_prefix(options->argc, options->argv, process_name_prefix);
}
# endif
# undef OPT_CTL_AUTO
# undef OPT_CTL_MANUAL
# undef OPT_CTL_DEFAULT_NOBREAK
# undef OPT_PARSE
# undef OPT_RESOLUTION
# undef OPT_NUMBER
# undef OPT_SET
return 0;
}
static int _parse_resolution(const char *str, unsigned *width, unsigned *height, bool limited) {
unsigned tmp_width;
unsigned tmp_height;
if (sscanf(str, "%ux%u", &tmp_width, &tmp_height) != 2) {
return -1;
}
if (limited) {
if (tmp_width < VIDEO_MIN_WIDTH || tmp_width > VIDEO_MAX_WIDTH) {
return -2;
}
if (tmp_height < VIDEO_MIN_HEIGHT || tmp_height > VIDEO_MAX_HEIGHT) {
return -3;
}
}
*width = tmp_width;
*height = tmp_height;
return 0;
}
#ifdef WITH_OMX
static int _parse_glitched_resolutions(const char *str, struct encoder_t *encoder) {
char *str_copy;
char *ptr;
unsigned count = 0;
unsigned width;
unsigned height;
assert((str_copy = strdup(str)) != NULL);
ptr = strtok(str_copy, ",;:\n\t ");
while (ptr != NULL) {
if (count >= MAX_GLITCHED_RESOLUTIONS) {
printf("Too big '--glitched-resolutions' list: maxlen=%u\n", MAX_GLITCHED_RESOLUTIONS);
goto error;
}
switch (_parse_resolution(ptr, &width, &height, true)) {
case -1:
printf("Invalid resolution format of '%s' in '--glitched-resolutions=%s\n", ptr, str_copy);
goto error;
case -2:
printf("Invalid width of '%s' in '--glitched-resolutions=%s: min=%u, max=%u\n",
ptr, str_copy, VIDEO_MIN_WIDTH, VIDEO_MIN_HEIGHT);
goto error;
case -3:
printf("Invalid width of '%s' in '--glitched-resolutions=%s: min=%u, max=%u\n",
ptr, str_copy, VIDEO_MIN_WIDTH, VIDEO_MIN_HEIGHT);
goto error;
case 0: break;
default: assert(0 && "Unknown error");
}
encoder->glitched_resolutions[count][0] = width;
encoder->glitched_resolutions[count][1] = height;
count += 1;
ptr = strtok(NULL, ",;:\n\t ");
}
encoder->n_glitched_resolutions = count;
free(str_copy);
return 0;
error:
free(str_copy);
return -1;
}
#endif
static void _features(void) {
# ifdef WITH_OMX
puts("+ WITH_OMX");
# else
puts("- WITH_OMX");
# endif
# ifdef WITH_GPIO
puts("+ WITH_GPIO");
# else
puts("- WITH_GPIO");
# endif
# ifdef WITH_PTHREAD_NP
puts("+ WITH_PTHREAD_NP");
# else
puts("- WITH_PTHREAD_NP");
# endif
# ifdef WITH_SETPROCTITLE
puts("+ WITH_SETPROCTITLE");
# else
puts("- WITH_SETPROCTITLE");
# endif
# ifdef HAS_PDEATHSIG
puts("+ HAS_PDEATHSIG");
# else
puts("- HAS_PDEATHSIG");
# endif
}
static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_server_t *server) {
printf("\nuStreamer - Lightweight and fast MJPG-HTTP streamer\n");
printf("═══════════════════════════════════════════════════\n\n");
printf("Version: %s; license: GPLv3\n", VERSION);
printf("Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com>\n\n");
printf("Capturing options:\n");
printf("══════════════════\n");
printf(" -d|--device </dev/path> ───────────── Path to V4L2 device. Default: %s.\n\n", dev->path);
printf(" -i|--input <N> ────────────────────── Input channel. Default: %u.\n\n", dev->input);
printf(" -r|--resolution <WxH> ─────────────── Initial image resolution. Default: %ux%u.\n\n", dev->width, dev->height);
printf(" -m|--format <fmt> ─────────────────── Image format.\n");
printf(" Available: %s; default: YUYV.\n\n", FORMATS_STR);
printf(" -a|--tv-standard <std> ────────────── Force TV standard.\n");
printf(" Available: %s; default: disabled.\n\n", STANDARDS_STR);
printf(" -I|--io-method <method> ───────────── Set V4L2 IO method (see kernel documentation).\n");
printf(" Changing of this parameter may increase the performance. Or not.\n");
printf(" Available: %s; default: MMAP\n\n", IO_METHODS_STR);
printf(" -f|--desired-fps <N> ──────────────── Desired FPS. Default: maximum possible.\n\n");
printf(" -z|--min-frame-size <N> ───────────── Drop frames smaller then this limit. Useful if the device\n");
printf(" produces small-sized garbage frames. Default: disabled.\n\n");
printf(" -n|--persistent ───────────────────── Don't re-initialize device on timeout. Default: disabled.\n\n");
printf(" -t|--dv-timings ───────────────────── Enable DV timings querying and events processing\n");
printf(" to automatic resolution change. Default: disabled.\n\n");
printf(" -b|--buffers <N> ──────────────────── The number of buffers to receive data from the device.\n");
printf(" Each buffer may processed using an independent thread.\n");
printf(" Default: %u (the number of CPU cores (but not more than 4) + 1).\n\n", dev->n_buffers);
printf(" -w|--workers <N> ──────────────────── The number of worker threads but not more than buffers.\n");
printf(" Default: %u (the number of CPU cores (but not more than 4)).\n\n", dev->n_workers);
printf(" -q|--quality <N> ──────────────────── Set quality of JPEG encoding from 1 to 100 (best). Default: %u.\n", encoder->quality);
printf(" Note: If HW encoding is used (JPEG source format selected),\n");
printf(" this parameter attempts to configure the camera\n");
printf(" or capture device hardware's internal encoder.\n");
printf(" It does not re-encode MJPG to MJPG to change the quality level\n");
printf(" for sources that already output MJPG.\n\n");
printf(" -c|--encoder <type> ───────────────── Use specified encoder. It may affect the number of workers.\n");
printf(" Available:\n");
printf(" * CPU ─ Software MJPG encoding (default);\n");
# ifdef WITH_OMX
printf(" * OMX ─ GPU hardware accelerated MJPG encoding with OpenMax;\n");
# endif
printf(" * HW ── Use pre-encoded MJPG frames directly from camera hardware.\n\n");
# ifdef WITH_OMX
printf(" -g|--glitched-resolutions <WxH,...> ─ Comma-separated list of resolutions that require forced\n");
printf(" encoding on CPU instead of OMX. Default: disabled.\n\n");
# endif
printf(" --device-timeout <sec> ────────────── Timeout for device querying. Default: %u.\n\n", dev->timeout);
printf(" --device-error-delay <sec> ────────── Delay before trying to connect to the device again\n");
printf(" after an error (timeout for example). Default: %u.\n\n", dev->error_delay);
printf("Image control options:\n");
printf("══════════════════════\n");
printf(" --image-default ────────────────────── Reset all image settings bellow to default. Default: no change.\n\n");
printf(" --brightness <N|auto|default> ──────── Set brightness. Default: no change.\n\n");
printf(" --contrast <N|default> ─────────────── Set contrast. Default: no change.\n\n");
printf(" --saturation <N|default> ───────────── Set saturation. Default: no change.\n\n");
printf(" --hue <N|auto|default> ─────────────── Set hue. Default: no change.\n\n");
printf(" --gamma <N|default> ─────────────────── Set gamma. Default: no change.\n\n");
printf(" --sharpness <N|default> ────────────── Set sharpness. Default: no change.\n\n");
printf(" --backlight-compensation <N|default> ─ Set backlight compensation. Default: no change.\n\n");
printf(" --white-balance <N|auto|default> ───── Set white balance. Default: no change.\n\n");
printf(" --gain <N|auto|default> ────────────── Set gain. Default: no change.\n\n");
printf(" Hint: use v4l2-ctl --list-ctrls-menus to query available controls of the device.\n\n");
printf("HTTP server options:\n");
printf("════════════════════\n");
printf(" -s|--host <address> ──────── Listen on Hostname or IP. Default: %s.\n\n", server->host);
printf(" -p|--port <N> ────────────── Bind to this TCP port. Default: %u.\n\n", server->port);
printf(" -U|--unix <path> ─────────── Bind to UNIX domain socket. Default: disabled.\n\n");
printf(" -D|--unix-rm ─────────────── Try to remove old UNIX socket file before binding. Default: disabled.\n\n");
printf(" -M|--unix-mode <mode> ────── Set UNIX socket file permissions (like 777). Default: disabled.\n\n");
printf(" --user <name> ────────────── HTTP basic auth user. Default: disabled.\n\n");
printf(" --passwd <str> ───────────── HTTP basic auth passwd. Default: empty.\n\n");
printf(" --static <path> ───────────── Path to dir with static files instead of embedded root index page.\n");
printf(" Symlinks are not supported for security reasons. Default: disabled.\n\n");
printf(" -k|--blank <path> ────────── Path to JPEG file that will be shown when the device is disconnected\n");
printf(" during the streaming. Default: black screen 640x480 with 'NO SIGNAL'.\n\n");
printf(" -K|--last-as-blank <sec> ─── Show the last frame received from the camera after it was disconnected,\n");
printf(" but no more than specified time (or endlessly if 0 is specified).\n");
printf(" If the device has not yet been online, display 'NO SIGNAL' or the image\n");
printf(" specified by option --blank. Default: disabled.\n\n");
printf(" -e|--drop-same-frames <N> ── Don't send identical frames to clients, but no more than specified number.\n");
printf(" It can significantly reduce the outgoing traffic, but will increase\n");
printf(" the CPU loading. Don't use this option with analog signal sources\n");
printf(" or webcams, it's useless. Default: disabled.\n\n");
printf(" -l|--slowdown ────────────── Slowdown capturing to 1 FPS or less when no stream clients are connected.\n");
printf(" Useful to reduce CPU consumption. Default: disabled.\n\n");
printf(" -R|--fake-resolution <WxH> ─ Override image resolution for the /state. Default: disabled.\n\n");
printf(" --server-timeout <sec> ───── Timeout for client connections. Default: %u.\n\n", server->timeout);
#ifdef WITH_GPIO
printf("GPIO options:\n");
printf("═════════════\n");
printf(" --gpio-prog-running <pin> ───── Set 1 on GPIO pin while uStreamer is running. Default: disabled.\n\n");
printf(" --gpio-stream-online <pin> ──── Set 1 while streaming. Default: disabled\n\n");
printf(" --gpio-has-http-clients <pin> ─ Set 1 while stream has at least one client. Default: disabled.\n\n");
printf(" --gpio-workers-busy-at <pin> ── Set 1 on (pin + N) while worker with number N has a job.\n");
printf(" The worker's numbering starts from 0. Default: disabled\n\n");
#endif
#if (defined(HAS_PDEATHSIG) || defined(WITH_SETPROCTITLE))
printf("Process options:\n");
printf("════════════════\n");
#endif
#ifdef HAS_PDEATHSIG
printf(" --exit-on-parent-death ─────── Exit the program if the parent process is dead. Default: disabled.\n\n");
#endif
#ifdef WITH_SETPROCTITLE
printf(" --process-name-prefix <str> ── Set process name prefix which will be displayed in the process list\n");
printf(" like '<str>: ustreamer --blah-blah-blah'. Default: disabled.\n\n");
printf(" --notify-parent ────────────── Send SIGUSR2 to the parent process when the stream parameters are changed.\n");
printf(" Checking changes is performed for the online flag and image resolution.\n\n");
#endif
printf("Logging options:\n");
printf("════════════════\n");
printf(" --log-level <N> ──── Verbosity level of messages from 0 (info) to 3 (debug).\n");
printf(" Enabling debugging messages can slow down the program.\n");
printf(" Available levels: 0 (info), 1 (performance), 2 (verbose), 3 (debug).\n");
printf(" Default: %d.\n\n", log_level);
printf(" --perf ───────────── Enable performance messages (same as --log-level=1). Default: disabled.\n\n");
printf(" --verbose ────────── Enable verbose messages and lower (same as --log-level=2). Default: disabled.\n\n");
printf(" --debug ──────────── Enable debug messages and lower (same as --log-level=3). Default: disabled.\n\n");
printf(" --force-log-colors ─ Force color logging. Default: colored if stdout is a TTY.\n\n");
printf(" --no-log-colors ──── Disable color logging. Default: ditto.\n\n");
printf("Help options:\n");
printf("═════════════\n");
printf(" -h|--help ─────── Print this text and exit.\n\n");
printf(" -v|--version ──── Print version and exit.\n\n");
printf(" --features ────── Print list of supporeted features.\n\n");
}

View File

@@ -1,98 +0,0 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "picture.h"
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include "tools.h"
#include "logging.h"
struct picture_t *picture_init(void) {
struct picture_t *picture;
A_CALLOC(picture, 1);
return picture;
}
void picture_destroy(struct picture_t *picture) {
if (picture->data) {
free(picture->data);
}
free(picture);
}
size_t picture_get_generous_size(unsigned width, unsigned height) {
return ((width * height) << 1) * 2;
}
void picture_realloc_data(struct picture_t *picture, size_t size) {
if (picture->allocated < size) {
LOG_DEBUG("Increasing picture 0x%p buffer: %zu -> %zu (+%zu)",
picture, picture->allocated, size, size - picture->allocated);
A_REALLOC(picture->data, size);
picture->allocated = size;
}
}
void picture_set_data(struct picture_t *picture, const unsigned char *data, size_t size) {
picture_realloc_data(picture, size);
memcpy(picture->data, data, size);
picture->used = size;
}
void picture_append_data(struct picture_t *picture, const unsigned char *data, size_t size) {
size_t new_used = picture->used + size;
picture_realloc_data(picture, new_used);
memcpy(picture->data + picture->used, data, size);
picture->used = new_used;
}
void picture_copy(const struct picture_t *src, struct picture_t *dest) {
picture_set_data(dest, src->data, src->used);
# define COPY(_field) dest->_field = src->_field
COPY(used);
COPY(width);
COPY(height);
COPY(grab_ts);
COPY(encode_begin_ts);
COPY(encode_end_ts);
# undef COPY
}
bool picture_compare(const struct picture_t *a, const struct picture_t *b) {
return (
a->allocated && b->allocated
&& a->used == b->used
&& !memcmp(a->data, b->data, b->used)
);
}

View File

@@ -1,562 +0,0 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "stream.h"
#include <stdlib.h>
#include <stdbool.h>
#include <stdatomic.h>
#include <unistd.h>
#include <errno.h>
#include <assert.h>
#include <pthread.h>
#include "tools.h"
#include "threading.h"
#include "logging.h"
#include "picture.h"
#include "device.h"
#include "encoder.h"
#ifdef WITH_GPIO
# include "gpio.h"
#endif
struct _worker_t {
pthread_t tid;
unsigned number;
atomic_bool *proc_stop;
atomic_bool *workers_stop;
long double last_comp_time;
pthread_mutex_t has_job_mutex;
unsigned buf_index;
atomic_bool has_job;
bool job_timely;
bool job_failed;
long double job_start_ts;
pthread_cond_t has_job_cond;
pthread_mutex_t *free_workers_mutex;
unsigned *free_workers;
pthread_cond_t *free_workers_cond;
struct _worker_t *order_prev;
struct _worker_t *order_next;
struct device_t *dev;
struct encoder_t *encoder;
};
struct _workers_pool_t {
unsigned n_workers;
struct _worker_t *workers;
struct _worker_t *oldest_worker;
struct _worker_t *latest_worker;
long double approx_comp_time;
pthread_mutex_t free_workers_mutex;
unsigned free_workers;
pthread_cond_t free_workers_cond;
atomic_bool workers_stop;
long double desired_frames_interval;
};
static struct _workers_pool_t *_stream_init_loop(struct stream_t *stream);
static struct _workers_pool_t *_stream_init(struct stream_t *stream);
static void _stream_expose_picture(struct stream_t *stream, unsigned buf_index, unsigned captured_fps);
static struct _workers_pool_t *_workers_pool_init(struct stream_t *stream);
static void _workers_pool_destroy(struct _workers_pool_t *pool);
static void *_worker_thread(void *v_worker);
static struct _worker_t *_workers_pool_wait(struct _workers_pool_t *pool);
static void _workers_pool_assign(struct _workers_pool_t *pool, struct _worker_t *ready_worker, unsigned buf_index);
static long double _workers_pool_get_fluency_delay(struct _workers_pool_t *pool, struct _worker_t *ready_worker);
struct stream_t *stream_init(struct device_t *dev, struct encoder_t *encoder) {
struct process_t *proc;
struct stream_t *stream;
A_CALLOC(proc, 1);
atomic_init(&proc->stop, false);
atomic_init(&proc->slowdown, false);
A_CALLOC(stream, 1);
stream->picture = picture_init();
stream->dev = dev;
stream->encoder = encoder;
atomic_init(&stream->updated, false);
A_MUTEX_INIT(&stream->mutex);
stream->proc = proc;
return stream;
}
void stream_destroy(struct stream_t *stream) {
A_MUTEX_DESTROY(&stream->mutex);
picture_destroy(stream->picture);
free(stream->proc);
free(stream);
}
void stream_loop(struct stream_t *stream) {
struct _workers_pool_t *pool;
LOG_INFO("Using V4L2 device: %s", stream->dev->path);
LOG_INFO("Using desired FPS: %u", stream->dev->desired_fps);
while ((pool = _stream_init_loop(stream)) != NULL) {
long double grab_after = 0;
unsigned fluency_passed = 0;
unsigned captured_fps = 0;
unsigned captured_fps_accum = 0;
long long captured_fps_second = 0;
bool persistent_timeout_reported = false;
LOG_INFO("Capturing ...");
LOG_DEBUG("Pre-allocating memory for stream picture ...");
picture_realloc_data(stream->picture, picture_get_generous_size(stream->dev->run->width, stream->dev->run->height));
while (!atomic_load(&stream->proc->stop)) {
struct _worker_t *ready_worker;
SEP_DEBUG('-');
LOG_DEBUG("Waiting for worker ...");
ready_worker = _workers_pool_wait(pool);
if (!ready_worker->job_failed) {
if (ready_worker->job_timely) {
_stream_expose_picture(stream, ready_worker->buf_index, captured_fps);
LOG_PERF("##### Encoded picture exposed; worker=%u", ready_worker->number);
} else {
LOG_PERF("----- Encoded picture dropped; worker=%u", ready_worker->number);
}
} else {
break;
}
if (atomic_load(&stream->proc->stop)) {
break;
}
if (atomic_load(&stream->proc->slowdown)) {
usleep(1000000);
}
bool has_read;
bool has_write;
bool has_error;
int selected = device_select(stream->dev, &has_read, &has_write, &has_error);
if (selected < 0) {
if (errno != EINTR) {
LOG_PERROR("Mainloop select() error");
break;
}
} else if (selected == 0) {
# ifdef WITH_GPIO
GPIO_SET_LOW(stream_online);
# endif
if (stream->dev->persistent) {
if (!persistent_timeout_reported) {
LOG_ERROR("Mainloop select() timeout, polling ...")
persistent_timeout_reported = true;
}
continue;
} else {
LOG_ERROR("Mainloop select() timeout");
break;
}
} else {
persistent_timeout_reported = false;
if (has_read) {
LOG_DEBUG("Frame is ready");
# ifdef WITH_GPIO
GPIO_SET_HIGH(stream_online);
# endif
int buf_index;
long double now = get_now_monotonic();
long long now_second = floor_ms(now);
if ((buf_index = device_grab_buffer(stream->dev)) < 0) {
break;
}
// Workaround for broken, corrupted frames:
// Under low light conditions corrupted frames may get captured.
// The good thing is such frames are quite small compared to the regular pictures.
// For example a VGA (640x480) webcam picture is normally >= 8kByte large,
// corrupted frames are smaller.
if (stream->dev->run->hw_buffers[buf_index].used < stream->dev->min_frame_size) {
LOG_DEBUG("Dropped too small frame sized %zu bytes, assuming it was broken",
stream->dev->run->hw_buffers[buf_index].used);
goto pass_frame;
}
{
if (now < grab_after) {
fluency_passed += 1;
LOG_VERBOSE("Passed %u frames for fluency: now=%.03Lf, grab_after=%.03Lf", fluency_passed, now, grab_after);
goto pass_frame;
}
fluency_passed = 0;
if (now_second != captured_fps_second) {
captured_fps = captured_fps_accum;
captured_fps_accum = 0;
captured_fps_second = now_second;
LOG_PERF_FPS("A new second has come; captured_fps=%u", captured_fps);
}
captured_fps_accum += 1;
long double fluency_delay = _workers_pool_get_fluency_delay(pool, ready_worker);
grab_after = now + fluency_delay;
LOG_VERBOSE("Fluency: delay=%.03Lf, grab_after=%.03Lf", fluency_delay, grab_after);
}
_workers_pool_assign(pool, ready_worker, buf_index);
goto next_handlers; // Поток сам освободит буфер
pass_frame:
if (device_release_buffer(stream->dev, buf_index) < 0) {
break;
}
}
next_handlers:
if (has_write) {
LOG_ERROR("Got unexpected writing event, seems device was disconnected");
break;
}
if (has_error) {
LOG_INFO("Got V4L2 event");
if (device_consume_event(stream->dev) < 0) {
break;
}
}
}
}
A_MUTEX_LOCK(&stream->mutex);
stream->online = false;
atomic_store(&stream->updated, true);
A_MUTEX_UNLOCK(&stream->mutex);
_workers_pool_destroy(pool);
device_switch_capturing(stream->dev, false);
device_close(stream->dev);
# ifdef WITH_GPIO
GPIO_SET_LOW(stream_online);
# endif
}
}
void stream_loop_break(struct stream_t *stream) {
atomic_store(&stream->proc->stop, true);
}
void stream_switch_slowdown(struct stream_t *stream, bool slowdown) {
atomic_store(&stream->proc->slowdown, slowdown);
}
static struct _workers_pool_t *_stream_init_loop(struct stream_t *stream) {
struct _workers_pool_t *pool = NULL;
LOG_DEBUG("%s: stream->proc->stop=%d", __FUNCTION__, atomic_load(&stream->proc->stop));
while (!atomic_load(&stream->proc->stop)) {
SEP_INFO('=');
if ((pool = _stream_init(stream)) == NULL) {
LOG_INFO("Sleeping %u seconds before new stream init ...", stream->dev->error_delay);
sleep(stream->dev->error_delay);
} else {
break;
}
}
return pool;
}
static struct _workers_pool_t *_stream_init(struct stream_t *stream) {
if (device_open(stream->dev) < 0) {
goto error;
}
if (device_switch_capturing(stream->dev, true) < 0) {
goto error;
}
encoder_prepare(stream->encoder, stream->dev);
return _workers_pool_init(stream);
error:
device_close(stream->dev);
return NULL;
}
static void _stream_expose_picture(struct stream_t *stream, unsigned buf_index, unsigned captured_fps) {
A_MUTEX_LOCK(&stream->mutex);
picture_copy(stream->dev->run->pictures[buf_index], stream->picture);
stream->online = true;
stream->captured_fps = captured_fps;
atomic_store(&stream->updated, true);
A_MUTEX_UNLOCK(&stream->mutex);
}
static struct _workers_pool_t *_workers_pool_init(struct stream_t *stream) {
struct _workers_pool_t *pool;
LOG_INFO("Creating pool with %u workers ...", stream->dev->run->n_workers);
A_CALLOC(pool, 1);
pool->n_workers = stream->dev->run->n_workers;
A_CALLOC(pool->workers, pool->n_workers);
A_MUTEX_INIT(&pool->free_workers_mutex);
A_COND_INIT(&pool->free_workers_cond);
atomic_init(&pool->workers_stop, false);
if (stream->dev->desired_fps > 0) {
pool->desired_frames_interval = (long double)1 / stream->dev->desired_fps;
}
for (unsigned number = 0; number < pool->n_workers; ++number) {
# define WORKER(_next) pool->workers[number]._next
A_MUTEX_INIT(&WORKER(has_job_mutex));
atomic_init(&WORKER(has_job), false);
A_COND_INIT(&WORKER(has_job_cond));
WORKER(number) = number;
WORKER(proc_stop) = &stream->proc->stop;
WORKER(workers_stop) = &pool->workers_stop;
WORKER(free_workers_mutex) = &pool->free_workers_mutex;
WORKER(free_workers) = &pool->free_workers;
WORKER(free_workers_cond) = &pool->free_workers_cond;
WORKER(dev) = stream->dev;
WORKER(encoder) = stream->encoder;
A_THREAD_CREATE(&WORKER(tid), _worker_thread, (void *)&(pool->workers[number]));
pool->free_workers += 1;
# undef WORKER
}
return pool;
}
static void _workers_pool_destroy(struct _workers_pool_t *pool) {
LOG_INFO("Destroying workers pool ...");
atomic_store(&pool->workers_stop, true);
for (unsigned number = 0; number < pool->n_workers; ++number) {
# define WORKER(_next) pool->workers[number]._next
A_MUTEX_LOCK(&WORKER(has_job_mutex));
atomic_store(&WORKER(has_job), true); // Final job: die
A_MUTEX_UNLOCK(&WORKER(has_job_mutex));
A_COND_SIGNAL(&WORKER(has_job_cond));
A_THREAD_JOIN(WORKER(tid));
A_MUTEX_DESTROY(&WORKER(has_job_mutex));
A_COND_DESTROY(&WORKER(has_job_cond));
# undef WORKER
}
A_MUTEX_DESTROY(&pool->free_workers_mutex);
A_COND_DESTROY(&pool->free_workers_cond);
free(pool->workers);
free(pool);
}
static void *_worker_thread(void *v_worker) {
struct _worker_t *worker = (struct _worker_t *)v_worker;
A_THREAD_RENAME("worker-%u", worker->number);
LOG_DEBUG("Hello! I am a worker #%u ^_^", worker->number);
# ifdef WITH_GPIO
GPIO_INIT_PIN(workers_busy_at, worker->number);
# endif
while (!atomic_load(worker->proc_stop) && !atomic_load(worker->workers_stop)) {
LOG_DEBUG("Worker %u waiting for a new job ...", worker->number);
# ifdef WITH_GPIO
GPIO_SET_LOW_AT(workers_busy_at, worker->number);
# endif
A_MUTEX_LOCK(&worker->has_job_mutex);
A_COND_WAIT_TRUE(atomic_load(&worker->has_job), &worker->has_job_cond, &worker->has_job_mutex);
A_MUTEX_UNLOCK(&worker->has_job_mutex);
if (!atomic_load(worker->workers_stop)) {
# define PICTURE(_next) worker->dev->run->pictures[worker->buf_index]->_next
LOG_DEBUG("Worker %u compressing JPEG from buffer %u ...", worker->number, worker->buf_index);
# ifdef WITH_GPIO
GPIO_SET_HIGH_AT(workers_busy_at, worker->number);
# endif
if (encoder_compress_buffer(worker->encoder, worker->dev, worker->number, worker->buf_index) < 0) {
worker->job_failed = false;
}
if (device_release_buffer(worker->dev, worker->buf_index) == 0) {
worker->job_start_ts = PICTURE(encode_begin_ts);
atomic_store(&worker->has_job, false);
worker->last_comp_time = PICTURE(encode_end_ts) - worker->job_start_ts;
LOG_VERBOSE("Compressed new JPEG: size=%zu, time=%0.3Lf, worker=%u, buffer=%u",
PICTURE(used), worker->last_comp_time, worker->number, worker->buf_index);
} else {
worker->job_failed = true;
atomic_store(&worker->has_job, false);
}
# undef PICTURE
}
A_MUTEX_LOCK(worker->free_workers_mutex);
*worker->free_workers += 1;
A_MUTEX_UNLOCK(worker->free_workers_mutex);
A_COND_SIGNAL(worker->free_workers_cond);
}
LOG_DEBUG("Bye-bye (worker %u)", worker->number);
# ifdef WITH_GPIO
GPIO_SET_LOW_AT(workers_busy_at, worker->number);
# endif
return NULL;
}
static struct _worker_t *_workers_pool_wait(struct _workers_pool_t *pool) {
struct _worker_t *ready_worker = NULL;
A_MUTEX_LOCK(&pool->free_workers_mutex);
A_COND_WAIT_TRUE(pool->free_workers, &pool->free_workers_cond, &pool->free_workers_mutex);
A_MUTEX_UNLOCK(&pool->free_workers_mutex);
if (pool->oldest_worker && !atomic_load(&pool->oldest_worker->has_job)) {
ready_worker = pool->oldest_worker;
ready_worker->job_timely = true;
pool->oldest_worker = pool->oldest_worker->order_next;
} else {
for (unsigned number = 0; number < pool->n_workers; ++number) {
if (
!atomic_load(&pool->workers[number].has_job) && (
ready_worker == NULL
|| ready_worker->job_start_ts < pool->workers[number].job_start_ts
)
) {
ready_worker = &pool->workers[number];
break;
}
}
assert(ready_worker != NULL);
ready_worker->job_timely = false; // Освободился воркер, получивший задание позже (или самый первый при самом первом захвате)
}
return ready_worker;
}
static void _workers_pool_assign(struct _workers_pool_t *pool, struct _worker_t *ready_worker, unsigned buf_index) {
if (pool->oldest_worker == NULL) {
pool->oldest_worker = ready_worker;
pool->latest_worker = pool->oldest_worker;
} else {
if (ready_worker->order_next) {
ready_worker->order_next->order_prev = ready_worker->order_prev;
}
if (ready_worker->order_prev) {
ready_worker->order_prev->order_next = ready_worker->order_next;
}
ready_worker->order_prev = pool->latest_worker;
pool->latest_worker->order_next = ready_worker;
pool->latest_worker = ready_worker;
}
pool->latest_worker->order_next = NULL;
A_MUTEX_LOCK(&ready_worker->has_job_mutex);
ready_worker->buf_index = buf_index;
atomic_store(&ready_worker->has_job, true);
A_MUTEX_UNLOCK(&ready_worker->has_job_mutex);
A_COND_SIGNAL(&ready_worker->has_job_cond);
A_MUTEX_LOCK(&pool->free_workers_mutex);
pool->free_workers -= 1;
A_MUTEX_UNLOCK(&pool->free_workers_mutex);
LOG_DEBUG("Assigned new frame in buffer %u to worker %u", buf_index, ready_worker->number);
}
static long double _workers_pool_get_fluency_delay(struct _workers_pool_t *pool, struct _worker_t *ready_worker) {
long double approx_comp_time;
long double min_delay;
approx_comp_time = pool->approx_comp_time * 0.9 + ready_worker->last_comp_time * 0.1;
LOG_VERBOSE("Correcting approx_comp_time: %.3Lf -> %.3Lf (last_comp_time=%.3Lf)",
pool->approx_comp_time, approx_comp_time, ready_worker->last_comp_time);
pool->approx_comp_time = approx_comp_time;
min_delay = pool->approx_comp_time / pool->n_workers; // Среднее время работы размазывается на N воркеров
if (pool->desired_frames_interval > 0 && min_delay > 0 && pool->desired_frames_interval > min_delay) {
// Искусственное время задержки на основе желаемого FPS, если включен --desired-fps
return pool->desired_frames_interval;
}
return min_delay;
}

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -22,36 +22,15 @@
#include "blank.h"
#include <stdio.h>
#include <stdbool.h>
#include <setjmp.h>
#include <jpeglib.h>
#include "../tools.h"
#include "../logging.h"
#include "../picture.h"
#include "data/blank_jpeg.h"
static frame_s *_init_internal(void);
static frame_s *_init_external(const char *path);
struct _jpeg_error_manager_t {
struct jpeg_error_mgr mgr; // Default manager
jmp_buf jmp;
};
frame_s *blank_frame_init(const char *path) {
frame_s *blank = NULL;
static struct picture_t *_init_internal(void);
static struct picture_t *_init_external(const char *path);
static int _jpeg_read_geometry(FILE *fp, unsigned *width, unsigned *height);
static void _jpeg_error_handler(j_common_ptr jpeg);
struct picture_t *blank_picture_init(const char *path) {
struct picture_t *blank = NULL;
if (path) {
if (path && path[0] != '\0') {
blank = _init_external(path);
}
@@ -64,40 +43,30 @@ struct picture_t *blank_picture_init(const char *path) {
return blank;
}
static struct picture_t *_init_internal(void) {
struct picture_t *blank;
blank = picture_init();
picture_set_data(blank, BLANK_JPEG_DATA, ARRAY_LEN(BLANK_JPEG_DATA));
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;
blank->format = V4L2_PIX_FMT_JPEG;
return blank;
}
static struct picture_t *_init_external(const char *path) {
static frame_s *_init_external(const char *path) {
FILE *fp = NULL;
struct picture_t *blank;
blank = picture_init();
frame_s *blank = frame_init("blank_external");
blank->format = V4L2_PIX_FMT_JPEG;
if ((fp = fopen(path, "rb")) == NULL) {
LOG_PERROR("Can't open blank placeholder '%s'", path);
goto error;
}
if (_jpeg_read_geometry(fp, &blank->width, &blank->height) < 0) {
goto error;
}
if (fseek(fp, 0, SEEK_SET) < 0) {
LOG_PERROR("Can't seek to begin of the blank placeholder");
goto error;
}
# define CHUNK_SIZE ((size_t)(100 * 1024))
while (true) {
if (blank->used + CHUNK_SIZE >= blank->allocated) {
picture_realloc_data(blank, blank->used + CHUNK_SIZE * 2);
frame_realloc_data(blank, blank->used + CHUNK_SIZE * 2);
}
size_t readed = fread(blank->data + blank->used, 1, CHUNK_SIZE, fp);
@@ -105,7 +74,7 @@ static struct picture_t *_init_external(const char *path) {
if (readed < CHUNK_SIZE) {
if (feof(fp)) {
goto ok;
break;
} else {
LOG_PERROR("Can't read blank placeholder");
goto error;
@@ -114,8 +83,19 @@ static struct picture_t *_init_external(const char *path) {
}
# undef CHUNK_SIZE
frame_s *decoded = frame_init("blank_external_decoded");
if (unjpeg(blank, decoded, false) < 0) {
frame_destroy(decoded);
goto error;
}
blank->width = decoded->width;
blank->height = decoded->height;
frame_destroy(decoded);
goto ok;
error:
picture_destroy(blank);
frame_destroy(blank);
blank = NULL;
ok:
@@ -125,37 +105,3 @@ static struct picture_t *_init_external(const char *path) {
return blank;
}
static int _jpeg_read_geometry(FILE *fp, unsigned *width, unsigned *height) {
struct jpeg_decompress_struct jpeg;
struct _jpeg_error_manager_t jpeg_error;
jpeg_create_decompress(&jpeg);
// https://stackoverflow.com/questions/19857766/error-handling-in-libjpeg
jpeg.err = jpeg_std_error((struct jpeg_error_mgr *)&jpeg_error);
jpeg_error.mgr.error_exit = _jpeg_error_handler;
if (setjmp(jpeg_error.jmp) < 0) {
jpeg_destroy_decompress(&jpeg);
return -1;
}
jpeg_stdio_src(&jpeg, fp);
jpeg_read_header(&jpeg, TRUE);
jpeg_start_decompress(&jpeg);
*width = jpeg.output_width;
*height = jpeg.output_height;
jpeg_destroy_decompress(&jpeg);
return 0;
}
static void _jpeg_error_handler(j_common_ptr jpeg) {
struct _jpeg_error_manager_t *jpeg_error = (struct _jpeg_error_manager_t *)jpeg->err;
char msg[JMSG_LENGTH_MAX];
(*jpeg_error->mgr.format_message)(jpeg, msg);
LOG_ERROR("Invalid blank placeholder: %s", msg);
longjmp(jpeg_error->jmp, -1);
}

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 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,17 @@
#pragma once
#include "../picture.h"
#include <stdio.h>
#include <stdbool.h>
#include <linux/videodev2.h>
#include "../libs/tools.h"
#include "../libs/logging.h"
#include "../libs/frame.h"
#include "../libs/unjpeg.h"
#include "data/blank_jpeg.h"
struct picture_t *blank_picture_init(const char *path);
frame_s *blank_frame_init(const char *path);

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -19,14 +19,14 @@
# #
*****************************************************************************/
#pragma once
#include "blank_jpeg.h"
const unsigned BLANK_JPEG_WIDTH = 640;
const unsigned BLANK_JPEG_HEIGHT = 480;
const unsigned char BLANK_JPEG_DATA[] = {
const size_t BLANK_JPEG_DATA_SIZE = 13845;
const uint8_t BLANK_JPEG_DATA[] = {
0xFF, 0xD8, 0xFF, 0xE1, 0x09, 0x50, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x6E, 0x73, 0x2E, 0x61, 0x64, 0x6F, 0x62,
0x65, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x78, 0x61, 0x70, 0x2F, 0x31, 0x2E, 0x30, 0x2F, 0x00, 0x3C, 0x3F, 0x78, 0x70, 0x61,
0x63, 0x6B, 0x65, 0x74, 0x20, 0x62, 0x65, 0x67, 0x69, 0x6E, 0x3D, 0x22, 0xEF, 0xBB, 0xBF, 0x22, 0x20, 0x69, 0x64, 0x3D,

View File

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

View File

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

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -19,19 +19,17 @@
# #
*****************************************************************************/
#pragma once
#include "../../config.h"
#include "index_html.h"
const char HTML_INDEX_PAGE[] = " \
const char *const HTML_INDEX_PAGE = " \
<!DOCTYPE html> \
\
<html> \
<head> \
<meta charset=\"utf-8\" /> \
<title>uStreamer</title> \
<style>body {font-family: monospace;}</style> \
</head> \
\
<body> \
@@ -39,46 +37,61 @@ const char HTML_INDEX_PAGE[] = " \
<hr> \
<ul> \
<li> \
<a href=\"/state\"><b><samp>/state</samp></b></a><br> \
Get JSON structure with state of the server. \
<a href=\"/state\"><b>/state</b></a><br> \
Get JSON structure with the state of the server. \
</li> \
<br> \
<li> \
<a href=\"/snapshot\"><b><samp>/snapshot</samp></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><samp>/stream</samp></b></a><br> \
<a href=\"/stream\"><b>/stream</b></a><br> \
Get a live stream. Query params:<br> \
<br> \
<ul> \
<li> \
<b><samp>key=abc123</samp></b><br> \
User-defined key, which is part of cookie <samp>stream_client</samp>, which allows<br> \
the stream client to determine its identifier and view statistics using <a href=\"/state\"><samp>/state</samp></a>. \
<b>key=abc123</b><br> \
The user-defined key, which is part of cookie <i>stream_client</i>, which allows<br> \
the stream client to determine its identifier and view statistics using <a href=\"/state\">/state</a>. \
</li> \
<br> \
<li> \
<b><samp>extra_headers=1</samp></b><br> \
Add <samp>X-UStreamer-*</samp> headers to /stream handle (like on <a href=\"/snapshot\"><samp>/snapshot</samp></a>). \
<b>extra_headers=1</b><br> \
Add <i>X-UStreamer-*</i> headers to the <a href=\"/stream\">/stream</a> handle \
(like with the <a href=\"/snapshot\">/snapshot</a>). \
</li> \
<br> \
<li> \
<b><samp>advance_headers=1</samp></b><br> \
Enable workaround for Chromium/Blink \
<a href=\"https://bugs.chromium.org/p/chromium/issues/detail?id=527446\">Bug #527446</a>. \
<b>advance_headers=1</b><br> \
Enable workaround for the Chromium/Blink bug \
<a href=\"https://bugs.chromium.org/p/chromium/issues/detail?id=527446\">#527446</a>. \
</li> \
<br> \
<li> \
<b><samp>dual_final_frames=1</samp></b><br> \
Enable workaround for Safari/WebKit bug when using option <samp>--drop-same-frames</samp>.<br> \
<b>dual_final_frames=1</b><br> \
Enable workaround for the Safari/WebKit bug when using option <i>--drop-same-frames</i>.<br> \
Without this option, when the frame series is completed, WebKit-based browsers<br> \
renders the last frame with a delay. \
</li> \
<br> \
<li> \
<b>zero_data=1</b><br> \
Disables the actual sending of JPEG data and leaves only response headers. \
</li> \
</ul> \
</li> \
<br> \
<li> \
The mjpg-streamer compatibility layer:<br> \
<br> \
<ul> \
<li><a href=\"/?action=snapshot\">/?action=snapshot</a> as alias to the <a href=\"/snapshot\">/snapshot</a>.</li> \
<br> \
<li><a href=\"/?action=stream\">/?action=stream</a> as alias to the <a href=\"/stream\">/stream</a>.</li> \
</ul> \
</li> \
</ul> \
<br> \
<hr> \

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 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,9 @@
#pragma once
#include "../../device.h"
#include <sys/types.h>
#include "../../libs/config.h"
void cpu_encoder_compress_buffer(struct device_t *dev, unsigned index, unsigned quality);
extern const char *const HTML_INDEX_PAGE;

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -22,28 +22,6 @@
#include "device.h"
#include <stdlib.h>
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <assert.h>
#include <sys/select.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <linux/videodev2.h>
#include <linux/v4l2-controls.h>
#include "tools.h"
#include "logging.h"
#include "xioctl.h"
#include "picture.h"
static const struct {
const char *name;
@@ -76,58 +54,57 @@ static const struct {
};
static int _device_open_check_cap(struct device_t *dev);
static int _device_open_dv_timings(struct device_t *dev);
static int _device_apply_dv_timings(struct device_t *dev);
static int _device_open_format(struct device_t *dev);
static void _device_open_hw_fps(struct device_t *dev);
static int _device_open_io_method(struct device_t *dev);
static int _device_open_io_method_mmap(struct device_t *dev);
static int _device_open_io_method_userptr(struct device_t *dev);
static int _device_open_queue_buffers(struct device_t *dev);
static void _device_open_alloc_picbufs(struct device_t *dev);
static int _device_apply_resolution(struct device_t *dev, unsigned width, unsigned height);
static int _device_open_check_cap(device_s *dev);
static int _device_open_dv_timings(device_s *dev);
static int _device_apply_dv_timings(device_s *dev);
static int _device_open_format(device_s *dev, bool first);
static void _device_open_hw_fps(device_s *dev);
static void _device_open_jpeg_quality(device_s *dev);
static int _device_open_io_method(device_s *dev);
static int _device_open_io_method_mmap(device_s *dev);
static int _device_open_io_method_userptr(device_s *dev);
static int _device_open_queue_buffers(device_s *dev);
static int _device_apply_resolution(device_s *dev, unsigned width, unsigned height);
static void _device_apply_controls(struct device_t *dev);
static int _device_query_control(struct device_t *dev, struct v4l2_queryctrl *query, const char *name, unsigned cid, bool quiet);
static void _device_set_control(struct device_t *dev, struct v4l2_queryctrl *query, const char *name, unsigned cid, int value, bool quiet);
static void _device_apply_controls(device_s *dev);
static int _device_query_control(
device_s *dev, struct v4l2_queryctrl *query,
const char *name, unsigned cid, bool quiet);
static void _device_set_control(
device_s *dev, struct v4l2_queryctrl *query,
const char *name, unsigned cid, int value, bool quiet);
static const char *_format_to_string_fourcc(char *buf, size_t size, unsigned format);
static const char *_format_to_string_nullable(unsigned format);
static const char *_format_to_string_supported(unsigned format);
static const char *_standard_to_string(v4l2_std_id standard);
static const char *_io_method_to_string_supported(enum v4l2_memory io_method);
struct device_t *device_init(void) {
struct device_runtime_t *run;
struct device_t *dev;
long cores_sysconf;
unsigned cores_available;
#define RUN(_next) dev->run->_next
cores_sysconf = sysconf(_SC_NPROCESSORS_ONLN);
cores_sysconf = (cores_sysconf < 0 ? 0 : cores_sysconf);
cores_available = max_u(min_u(cores_sysconf, 4), 1);
device_s *device_init(void) {
device_runtime_s *run;
A_CALLOC(run, 1);
run->fd = -1;
device_s *dev;
A_CALLOC(dev, 1);
dev->path = "/dev/video0";
dev->width = 640;
dev->height = 480;
dev->format = V4L2_PIX_FMT_YUYV;
dev->jpeg_quality = 80;
dev->standard = V4L2_STD_UNKNOWN;
dev->n_buffers = cores_available + 1;
dev->n_workers = min_u(cores_available, dev->n_buffers);
dev->timeout = 1;
dev->error_delay = 1;
dev->io_method = V4L2_MEMORY_MMAP;
dev->n_bufs = get_cores_available() + 1;
dev->min_frame_size = 128;
dev->timeout = 1;
dev->run = run;
return dev;
}
void device_destroy(struct device_t *dev) {
void device_destroy(device_s *dev) {
free(dev->run);
free(dev);
}
@@ -159,12 +136,12 @@ int device_parse_io_method(const char *str) {
return IO_METHOD_UNKNOWN;
}
int device_open(struct device_t *dev) {
if ((dev->run->fd = open(dev->path, O_RDWR|O_NONBLOCK)) < 0) {
int device_open(device_s *dev) {
if ((RUN(fd) = open(dev->path, O_RDWR|O_NONBLOCK)) < 0) {
LOG_PERROR("Can't open device");
goto error;
}
LOG_INFO("Device fd=%d opened", dev->run->fd);
LOG_INFO("Device fd=%d opened", RUN(fd));
if (_device_open_check_cap(dev) < 0) {
goto error;
@@ -172,22 +149,20 @@ int device_open(struct device_t *dev) {
if (_device_open_dv_timings(dev) < 0) {
goto error;
}
if (_device_open_format(dev) < 0) {
if (_device_open_format(dev, true) < 0) {
goto error;
}
_device_open_hw_fps(dev);
_device_open_jpeg_quality(dev);
if (_device_open_io_method(dev) < 0) {
goto error;
}
if (_device_open_queue_buffers(dev) < 0) {
goto error;
}
_device_open_alloc_picbufs(dev);
_device_apply_controls(dev);
dev->run->n_workers = min_u(dev->run->n_buffers, dev->n_workers);
LOG_DEBUG("Device fd=%d initialized", dev->run->fd);
LOG_DEBUG("Device fd=%d initialized", RUN(fd));
return 0;
error:
@@ -195,77 +170,126 @@ int device_open(struct device_t *dev) {
return -1;
}
void device_close(struct device_t *dev) {
dev->run->n_workers = 0;
void device_close(device_s *dev) {
RUN(persistent_timeout_reported) = false;
if (dev->run->pictures) {
LOG_DEBUG("Releasing picture buffers ...");
for (unsigned index = 0; index < dev->run->n_buffers; ++index) {
picture_destroy(dev->run->pictures[index]);
}
free(dev->run->pictures);
dev->run->pictures = NULL;
}
if (RUN(hw_bufs)) {
LOG_DEBUG("Releasing device buffers ...");
for (unsigned index = 0; index < RUN(n_bufs); ++index) {
# define HW(_next) RUN(hw_bufs)[index]._next
if (dev->run->hw_buffers) {
LOG_DEBUG("Releasing HW buffers ...");
for (unsigned index = 0; index < dev->run->n_buffers; ++index) {
# define HW_BUFFER(_next) dev->run->hw_buffers[index]._next
# ifdef WITH_OMX
if (HW(vcsm_handle) > 0) {
vcsm_free(HW(vcsm_handle));
HW(vcsm_handle) = -1;
}
if (HW(dma_fd) >= 0) {
close(HW(dma_fd));
HW(dma_fd) = -1;
}
# endif
if (dev->io_method == V4L2_MEMORY_MMAP) {
if (HW_BUFFER(allocated) > 0 && HW_BUFFER(data) != MAP_FAILED) {
if (munmap(HW_BUFFER(data), HW_BUFFER(allocated)) < 0) {
if (HW(raw.allocated) > 0 && HW(raw.data) != MAP_FAILED) {
if (munmap(HW(raw.data), HW(raw.allocated)) < 0) {
LOG_PERROR("Can't unmap device buffer %u", index);
}
}
} else { // V4L2_MEMORY_USERPTR
if (HW_BUFFER(data)) {
free(HW_BUFFER(data));
if (HW(raw.data)) {
free(HW(raw.data));
}
}
A_MUTEX_DESTROY(&HW(grabbed_mutex));
# undef HW_BUFFER
# undef HW
}
dev->run->n_buffers = 0;
free(dev->run->hw_buffers);
dev->run->hw_buffers = NULL;
RUN(n_bufs) = 0;
free(RUN(hw_bufs));
RUN(hw_bufs) = NULL;
}
if (dev->run->fd >= 0) {
if (RUN(fd) >= 0) {
LOG_DEBUG("Closing device ...");
if (close(dev->run->fd) < 0) {
LOG_PERROR("Can't close device fd=%d", dev->run->fd);
if (close(RUN(fd)) < 0) {
LOG_PERROR("Can't close device fd=%d", RUN(fd));
} else {
LOG_INFO("Device fd=%d closed", dev->run->fd);
LOG_INFO("Device fd=%d closed", RUN(fd));
}
dev->run->fd = -1;
RUN(fd) = -1;
}
}
int device_switch_capturing(struct device_t *dev, bool enable) {
if (enable != dev->run->capturing) {
#ifdef WITH_OMX
int device_export_to_vcsm(device_s *dev) {
# define DMA_FD RUN(hw_bufs[index].dma_fd)
# define VCSM_HANDLE RUN(hw_bufs[index].vcsm_handle)
for (unsigned index = 0; index < RUN(n_bufs); ++index) {
struct v4l2_exportbuffer exp;
MEMSET_ZERO(exp);
exp.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
exp.index = index;
LOG_DEBUG("Calling ioctl(VIDIOC_EXPBUF) for buffer index=%u ...", index);
if (xioctl(RUN(fd), VIDIOC_EXPBUF, &exp) < 0) {
LOG_PERROR("Unable to export device buffer index=%u", index);
goto error;
}
DMA_FD = exp.fd;
LOG_DEBUG("Importing DMA buffer fd=%d into VCSM ...", DMA_FD);
int vcsm_handle = vcsm_import_dmabuf(DMA_FD, "v4l2_buf");
if (vcsm_handle <= 0) {
LOG_PERROR("Unable to import DMA buffer fd=%d into VCSM", DMA_FD);
goto error;
}
VCSM_HANDLE = vcsm_handle;
}
return 0;
error:
for (unsigned index = 0; index < RUN(n_bufs); ++index) {
if (VCSM_HANDLE > 0) {
vcsm_free(VCSM_HANDLE);
VCSM_HANDLE = -1;
}
if (DMA_FD >= 0) {
close(DMA_FD);
DMA_FD = -1;
}
}
return -1;
# undef VCSM_HANDLE
# undef DMA_FD
}
#endif
int device_switch_capturing(device_s *dev, bool enable) {
if (enable != RUN(capturing)) {
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
LOG_DEBUG("Calling ioctl(%s) ...", (enable ? "VIDIOC_STREAMON" : "VIDIOC_STREAMOFF"));
if (xioctl(dev->run->fd, (enable ? VIDIOC_STREAMON : VIDIOC_STREAMOFF), &type) < 0) {
if (xioctl(RUN(fd), (enable ? VIDIOC_STREAMON : VIDIOC_STREAMOFF), &type) < 0) {
LOG_PERROR("Unable to %s capturing", (enable ? "start" : "stop"));
if (enable) {
return -1;
}
}
dev->run->capturing = enable;
RUN(capturing) = enable;
LOG_INFO("Capturing %s", (enable ? "started" : "stopped"));
}
return 0;
}
int device_select(struct device_t *dev, bool *has_read, bool *has_write, bool *has_error) {
struct timeval timeout;
int device_select(device_s *dev, bool *has_read, bool *has_write, bool *has_error) {
int retval;
# define INIT_FD_SET(_set) \
fd_set _set; FD_ZERO(&_set); FD_SET(dev->run->fd, &_set);
fd_set _set; FD_ZERO(&_set); FD_SET(RUN(fd), &_set);
INIT_FD_SET(read_fds);
INIT_FD_SET(write_fds);
@@ -273,66 +297,124 @@ int device_select(struct device_t *dev, bool *has_read, bool *has_write, bool *h
# undef INIT_FD_SET
struct timeval timeout;
timeout.tv_sec = dev->timeout;
timeout.tv_usec = 0;
LOG_DEBUG("Calling select() on video device ...");
retval = select(dev->run->fd + 1, &read_fds, &write_fds, &error_fds, &timeout);
retval = select(RUN(fd) + 1, &read_fds, &write_fds, &error_fds, &timeout);
if (retval > 0) {
*has_read = FD_ISSET(dev->run->fd, &read_fds);
*has_write = FD_ISSET(dev->run->fd, &write_fds);
*has_error = FD_ISSET(dev->run->fd, &error_fds);
*has_read = FD_ISSET(RUN(fd), &read_fds);
*has_write = FD_ISSET(RUN(fd), &write_fds);
*has_error = FD_ISSET(RUN(fd), &error_fds);
} else {
*has_read = false;
*has_write = false;
*has_error = false;
}
LOG_DEBUG("Device select() --> %d", retval);
if (retval > 0) {
RUN(persistent_timeout_reported) = false;
} else if (retval == 0) {
if (dev->persistent) {
if (!RUN(persistent_timeout_reported)) {
LOG_ERROR("Persistent device timeout (unplugged)");
RUN(persistent_timeout_reported) = true;
}
} else {
// Если устройство не персистентное, то таймаут является ошибкой
retval = -1;
}
}
return retval;
}
int device_grab_buffer(struct device_t *dev) {
struct v4l2_buffer buf_info;
int device_grab_buffer(device_s *dev, hw_buffer_s **hw) {
*hw = NULL;
struct v4l2_buffer buf_info;
MEMSET_ZERO(buf_info);
buf_info.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf_info.memory = dev->io_method;
LOG_DEBUG("Calling ioctl(VIDIOC_DQBUF) ...");
if (xioctl(dev->run->fd, VIDIOC_DQBUF, &buf_info) < 0) {
LOG_PERROR("Unable to dequeue buffer");
LOG_DEBUG("Grabbing device buffer ...");
if (xioctl(RUN(fd), VIDIOC_DQBUF, &buf_info) < 0) {
LOG_PERROR("Unable to grab device buffer");
return -1;
}
LOG_DEBUG("Got a new frame in buffer: index=%u, bytesused=%u", buf_info.index, buf_info.bytesused);
if (buf_info.index >= dev->run->n_buffers) {
LOG_ERROR("Got invalid buffer: index=%u, nbuffers=%u", buf_info.index, dev->run->n_buffers);
LOG_DEBUG("Grabbed new frame in device buffer: index=%u, bytesused=%u",
buf_info.index, buf_info.bytesused);
if (buf_info.index >= RUN(n_bufs)) {
LOG_ERROR("V4L2 error: grabbed invalid device buffer: index=%u, n_bufs=%u",
buf_info.index, RUN(n_bufs));
return -1;
}
dev->run->hw_buffers[buf_info.index].used = buf_info.bytesused;
memcpy(&dev->run->hw_buffers[buf_info.index].buf_info, &buf_info, sizeof(struct v4l2_buffer));
dev->run->pictures[buf_info.index]->grab_ts = get_now_monotonic();
// Workaround for broken, corrupted frames:
// Under low light conditions corrupted frames may get captured.
// The good thing is such frames are quite small compared to the regular frames.
// For example a VGA (640x480) webcam frame is normally >= 8kByte large,
// corrupted frames are smaller.
if (buf_info.bytesused < dev->min_frame_size) {
LOG_DEBUG("Dropped too small frame sized %d bytes, assuming it was broken", buf_info.bytesused);
LOG_DEBUG("Releasing device buffer index=%u (broken frame) ...", buf_info.index);
if (xioctl(RUN(fd), VIDIOC_QBUF, &buf_info) < 0) {
LOG_PERROR("Unable to release device buffer index=%u (broken frame)", buf_info.index);
return -1;
}
return -2;
}
# define HW(_next) RUN(hw_bufs)[buf_info.index]._next
A_MUTEX_LOCK(&HW(grabbed_mutex));
if (HW(grabbed)) {
LOG_ERROR("V4L2 error: grabbed device buffer is already used: index=%u, bytesused=%u",
buf_info.index, buf_info.bytesused);
A_MUTEX_UNLOCK(&HW(grabbed_mutex));
return -1;
}
HW(grabbed) = true;
A_MUTEX_UNLOCK(&HW(grabbed_mutex));
HW(raw.used) = buf_info.bytesused;
HW(raw.width) = RUN(width);
HW(raw.height) = RUN(height);
HW(raw.format) = RUN(format);
HW(raw.stride) = RUN(stride);
HW(raw.online) = true;
memcpy(&HW(buf_info), &buf_info, sizeof(struct v4l2_buffer));
HW(raw.grab_ts) = get_now_monotonic();
# undef HW
*hw = &RUN(hw_bufs[buf_info.index]);
return buf_info.index;
}
int device_release_buffer(struct device_t *dev, unsigned index) {
LOG_DEBUG("Calling ioctl(VIDIOC_QBUF) ...");
if (xioctl(dev->run->fd, VIDIOC_QBUF, &dev->run->hw_buffers[index].buf_info) < 0) {
LOG_PERROR("Unable to requeue buffer");
int device_release_buffer(device_s *dev, hw_buffer_s *hw) {
const unsigned index = hw->buf_info.index;
LOG_DEBUG("Releasing device buffer index=%u ...", index);
A_MUTEX_LOCK(&hw->grabbed_mutex);
if (xioctl(RUN(fd), VIDIOC_QBUF, &hw->buf_info) < 0) {
LOG_PERROR("Unable to release device buffer index=%u", index);
A_MUTEX_UNLOCK(&hw->grabbed_mutex);
return -1;
}
dev->run->hw_buffers[index].used = 0;
hw->grabbed = false;
A_MUTEX_UNLOCK(&hw->grabbed_mutex);
return 0;
}
int device_consume_event(struct device_t *dev) {
int device_consume_event(device_s *dev) {
struct v4l2_event event;
LOG_DEBUG("Calling ioctl(VIDIOC_DQEVENT) ...");
if (xioctl(dev->run->fd, VIDIOC_DQEVENT, &event) == 0) {
if (xioctl(RUN(fd), VIDIOC_DQEVENT, &event) == 0) {
switch (event.type) {
case V4L2_EVENT_SOURCE_CHANGE:
LOG_INFO("Got V4L2_EVENT_SOURCE_CHANGE: source changed");
@@ -347,14 +429,12 @@ int device_consume_event(struct device_t *dev) {
return 0;
}
static int _device_open_check_cap(struct device_t *dev) {
static int _device_open_check_cap(device_s *dev) {
struct v4l2_capability cap;
int input = dev->input; // Needs pointer to int for ioctl()
MEMSET_ZERO(cap);
LOG_DEBUG("Calling ioctl(VIDIOC_QUERYCAP) ...");
if (xioctl(dev->run->fd, VIDIOC_QUERYCAP, &cap) < 0) {
if (xioctl(RUN(fd), VIDIOC_QUERYCAP, &cap) < 0) {
LOG_PERROR("Can't query device (VIDIOC_QUERYCAP)");
return -1;
}
@@ -369,25 +449,26 @@ static int _device_open_check_cap(struct device_t *dev) {
return -1;
}
int input = dev->input; // Needs a pointer to int for ioctl()
LOG_INFO("Using input channel: %d", input);
if (xioctl(dev->run->fd, VIDIOC_S_INPUT, &input) < 0) {
if (xioctl(RUN(fd), VIDIOC_S_INPUT, &input) < 0) {
LOG_ERROR("Can't set input channel");
return -1;
}
if (dev->standard != V4L2_STD_UNKNOWN) {
LOG_INFO("Using TV standard: %s", _standard_to_string(dev->standard));
if (xioctl(dev->run->fd, VIDIOC_S_STD, &dev->standard) < 0) {
if (xioctl(RUN(fd), VIDIOC_S_STD, &dev->standard) < 0) {
LOG_ERROR("Can't set video standard");
return -1;
}
} else {
LOG_INFO("Using TV standard: DEFAULT");
LOG_DEBUG("Using TV standard: DEFAULT");
}
return 0;
}
static int _device_open_dv_timings(struct device_t *dev) {
static int _device_open_dv_timings(device_s *dev) {
_device_apply_resolution(dev, dev->width, dev->height);
if (dev->dv_timings) {
LOG_DEBUG("Using DV timings");
@@ -402,7 +483,7 @@ static int _device_open_dv_timings(struct device_t *dev) {
sub.type = V4L2_EVENT_SOURCE_CHANGE;
LOG_DEBUG("Calling ioctl(VIDIOC_SUBSCRIBE_EVENT) ...");
if (xioctl(dev->run->fd, VIDIOC_SUBSCRIBE_EVENT, &sub) < 0) {
if (xioctl(RUN(fd), VIDIOC_SUBSCRIBE_EVENT, &sub) < 0) {
LOG_PERROR("Can't subscribe to V4L2_EVENT_SOURCE_CHANGE");
return -1;
}
@@ -410,18 +491,17 @@ static int _device_open_dv_timings(struct device_t *dev) {
return 0;
}
static int _device_apply_dv_timings(struct device_t *dev) {
static int _device_apply_dv_timings(device_s *dev) {
struct v4l2_dv_timings dv;
MEMSET_ZERO(dv);
LOG_DEBUG("Calling ioctl(VIDIOC_QUERY_DV_TIMINGS) ...");
if (xioctl(dev->run->fd, VIDIOC_QUERY_DV_TIMINGS, &dv) == 0) {
if (xioctl(RUN(fd), VIDIOC_QUERY_DV_TIMINGS, &dv) == 0) {
LOG_INFO("Got new DV timings: resolution=%ux%u, pixclk=%llu",
dv.bt.width, dv.bt.height, (unsigned long long)dv.bt.pixelclock); // Issue #11
LOG_DEBUG("Calling ioctl(VIDIOC_S_DV_TIMINGS) ...");
if (xioctl(dev->run->fd, VIDIOC_S_DV_TIMINGS, &dv) < 0) {
if (xioctl(RUN(fd), VIDIOC_S_DV_TIMINGS, &dv) < 0) {
LOG_PERROR("Failed to set DV timings");
return -1;
}
@@ -432,9 +512,9 @@ static int _device_apply_dv_timings(struct device_t *dev) {
} else {
LOG_DEBUG("Calling ioctl(VIDIOC_QUERYSTD) ...");
if (xioctl(dev->run->fd, VIDIOC_QUERYSTD, &dev->standard) == 0) {
if (xioctl(RUN(fd), VIDIOC_QUERYSTD, &dev->standard) == 0) {
LOG_INFO("Applying the new VIDIOC_S_STD: %s ...", _standard_to_string(dev->standard));
if (xioctl(dev->run->fd, VIDIOC_S_STD, &dev->standard) < 0) {
if (xioctl(RUN(fd), VIDIOC_S_STD, &dev->standard) < 0) {
LOG_PERROR("Can't set video standard");
return -1;
}
@@ -443,70 +523,76 @@ static int _device_apply_dv_timings(struct device_t *dev) {
return 0;
}
static int _device_open_format(struct device_t *dev) {
struct v4l2_format fmt;
static int _device_open_format(device_s *dev, bool first) {
const unsigned stride = align_size(RUN(width), 32) << 1;
struct v4l2_format fmt;
MEMSET_ZERO(fmt);
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = dev->run->width;
fmt.fmt.pix.height = dev->run->height;
fmt.fmt.pix.width = RUN(width);
fmt.fmt.pix.height = RUN(height);
fmt.fmt.pix.pixelformat = dev->format;
fmt.fmt.pix.field = V4L2_FIELD_ANY;
fmt.fmt.pix.bytesperline = stride;
// Set format
LOG_DEBUG("Calling ioctl(VIDIOC_S_FMT) ...");
if (xioctl(dev->run->fd, VIDIOC_S_FMT, &fmt) < 0) {
LOG_PERROR("Unable to set pixelformat=%s, resolution=%ux%u",
_format_to_string_supported(dev->format),
dev->run->width,
dev->run->height);
if (xioctl(RUN(fd), VIDIOC_S_FMT, &fmt) < 0) {
LOG_PERROR("Unable to set pixelformat=%s, stride=%u, resolution=%ux%u",
_format_to_string_supported(dev->format), stride, RUN(width), RUN(height));
return -1;
}
// Check resolution
if (fmt.fmt.pix.width != dev->run->width || fmt.fmt.pix.height != dev->run->height) {
LOG_ERROR("Requested resolution=%ux%u is unavailable", dev->run->width, dev->run->height);
bool retry = false;
if (fmt.fmt.pix.width != RUN(width) || fmt.fmt.pix.height != RUN(height)) {
LOG_ERROR("Requested resolution=%ux%u is unavailable", RUN(width), RUN(height));
retry = true;
}
if (_device_apply_resolution(dev, fmt.fmt.pix.width, fmt.fmt.pix.height) < 0) {
return -1;
}
LOG_INFO("Using resolution: %ux%u", dev->run->width, dev->run->height);
if (first && retry) {
return _device_open_format(dev, false);
}
LOG_INFO("Using resolution: %ux%u", RUN(width), RUN(height));
// Check format
if (fmt.fmt.pix.pixelformat != dev->format) {
char format_obtained_str[8];
char *format_str_nullable;
LOG_ERROR("Could not obtain the requested pixelformat=%s; driver gave us %s",
_format_to_string_supported(dev->format),
_format_to_string_supported(fmt.fmt.pix.pixelformat));
if ((format_str_nullable = (char *)_format_to_string_nullable(fmt.fmt.pix.pixelformat)) != NULL) {
LOG_INFO("Falling back to pixelformat=%s", format_str_nullable);
char *format_str;
if ((format_str = (char *)_format_to_string_nullable(fmt.fmt.pix.pixelformat)) != NULL) {
LOG_INFO("Falling back to pixelformat=%s", format_str);
} else {
char fourcc_str[8];
LOG_ERROR("Unsupported pixelformat=%s (fourcc)",
_format_to_string_fourcc(format_obtained_str, 8, fmt.fmt.pix.pixelformat));
fourcc_to_string(fmt.fmt.pix.pixelformat, fourcc_str, 8));
return -1;
}
}
dev->run->format = fmt.fmt.pix.pixelformat;
LOG_INFO("Using pixelformat: %s", _format_to_string_supported(dev->run->format));
RUN(format) = fmt.fmt.pix.pixelformat;
LOG_INFO("Using pixelformat: %s", _format_to_string_supported(RUN(format)));
dev->run->raw_size = fmt.fmt.pix.sizeimage; // Only for userptr
RUN(stride) = fmt.fmt.pix.bytesperline;
RUN(raw_size) = fmt.fmt.pix.sizeimage; // Only for userptr
return 0;
}
static void _device_open_hw_fps(struct device_t *dev) {
struct v4l2_streamparm setfps;
static void _device_open_hw_fps(device_s *dev) {
RUN(hw_fps) = 0;
struct v4l2_streamparm setfps;
MEMSET_ZERO(setfps);
setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
LOG_DEBUG("Calling ioctl(VIDIOC_G_PARM) ...");
if (xioctl(dev->run->fd, VIDIOC_G_PARM, &setfps) < 0) {
if (xioctl(RUN(fd), VIDIOC_G_PARM, &setfps) < 0) {
if (errno == ENOTTY) { // Quiet message for Auvidea B101
LOG_INFO("Quierying HW FPS changing is not supported");
LOG_INFO("Querying HW FPS changing is not supported");
} else {
LOG_PERROR("Unable to query HW FPS changing");
}
@@ -525,21 +611,54 @@ static void _device_open_hw_fps(struct device_t *dev) {
SETFPS_TPF(numerator) = 1;
SETFPS_TPF(denominator) = (dev->desired_fps == 0 ? 255 : dev->desired_fps);
if (xioctl(dev->run->fd, VIDIOC_S_PARM, &setfps) < 0) {
if (xioctl(RUN(fd), VIDIOC_S_PARM, &setfps) < 0) {
LOG_PERROR("Unable to set HW FPS");
return;
}
if (dev->desired_fps != SETFPS_TPF(denominator)) {
LOG_INFO("Using HW FPS: %u -> %u (coerced)", dev->desired_fps, SETFPS_TPF(denominator));
if (SETFPS_TPF(numerator) != 1) {
LOG_ERROR("Invalid HW FPS numerator: %u != 1", SETFPS_TPF(numerator));
return;
}
if (SETFPS_TPF(denominator) == 0) { // Не знаю, бывает ли так, но пускай на всякий случай
LOG_ERROR("Invalid HW FPS denominator: 0");
return;
}
RUN(hw_fps) = SETFPS_TPF(denominator);
if (dev->desired_fps != RUN(hw_fps)) {
LOG_INFO("Using HW FPS: %u -> %u (coerced)", dev->desired_fps, RUN(hw_fps));
} else {
LOG_INFO("Using HW FPS: %u", dev->desired_fps);
LOG_INFO("Using HW FPS: %u", RUN(hw_fps));
}
# undef SETFPS_TPF
}
static int _device_open_io_method(struct device_t *dev) {
static void _device_open_jpeg_quality(device_s *dev) {
unsigned quality = 0;
if (is_jpeg(RUN(format))) {
struct v4l2_jpegcompression comp;
MEMSET_ZERO(comp);
if (xioctl(RUN(fd), VIDIOC_G_JPEGCOMP, &comp) < 0) {
LOG_ERROR("Device does not support setting of HW encoding quality parameters");
} else {
comp.quality = dev->jpeg_quality;
if (xioctl(RUN(fd), VIDIOC_S_JPEGCOMP, &comp) < 0) {
LOG_ERROR("Unable to change MJPG quality for JPEG source with HW pass-through encoder");
} else {
quality = dev->jpeg_quality;
}
}
}
RUN(jpeg_quality) = quality;
}
static int _device_open_io_method(device_s *dev) {
LOG_INFO("Using IO method: %s", _io_method_to_string_supported(dev->io_method));
switch (dev->io_method) {
case V4L2_MEMORY_MMAP: return _device_open_io_method_mmap(dev);
@@ -549,16 +668,15 @@ static int _device_open_io_method(struct device_t *dev) {
return -1;
}
static int _device_open_io_method_mmap(struct device_t *dev) {
static int _device_open_io_method_mmap(device_s *dev) {
struct v4l2_requestbuffers req;
MEMSET_ZERO(req);
req.count = dev->n_buffers;
req.count = dev->n_bufs;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
LOG_DEBUG("Calling ioctl(VIDIOC_REQBUFS) for V4L2_MEMORY_MMAP ...");
if (xioctl(dev->run->fd, VIDIOC_REQBUFS, &req) < 0) {
if (xioctl(RUN(fd), VIDIOC_REQBUFS, &req) < 0) {
LOG_PERROR("Device '%s' doesn't support V4L2_MEMORY_MMAP", dev->path);
return -1;
}
@@ -567,53 +685,62 @@ static int _device_open_io_method_mmap(struct device_t *dev) {
LOG_ERROR("Insufficient buffer memory: %u", req.count);
return -1;
} else {
LOG_INFO("Requested %u HW buffers, got %u", dev->n_buffers, req.count);
LOG_INFO("Requested %u device buffers, got %u", dev->n_bufs, req.count);
}
LOG_DEBUG("Allocating HW buffers ...");
LOG_DEBUG("Allocating device buffers ...");
A_CALLOC(dev->run->hw_buffers, req.count);
for (dev->run->n_buffers = 0; dev->run->n_buffers < req.count; ++dev->run->n_buffers) {
A_CALLOC(RUN(hw_bufs), req.count);
for (RUN(n_bufs) = 0; RUN(n_bufs) < req.count; ++RUN(n_bufs)) {
struct v4l2_buffer buf_info;
MEMSET_ZERO(buf_info);
buf_info.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf_info.memory = V4L2_MEMORY_MMAP;
buf_info.index = dev->run->n_buffers;
buf_info.index = RUN(n_bufs);
LOG_DEBUG("Calling ioctl(VIDIOC_QUERYBUF) for device buffer %u ...", dev->run->n_buffers);
if (xioctl(dev->run->fd, VIDIOC_QUERYBUF, &buf_info) < 0) {
LOG_DEBUG("Calling ioctl(VIDIOC_QUERYBUF) for device buffer %u ...", RUN(n_bufs));
if (xioctl(RUN(fd), VIDIOC_QUERYBUF, &buf_info) < 0) {
LOG_PERROR("Can't VIDIOC_QUERYBUF");
return -1;
}
# define HW_BUFFER(_next) dev->run->hw_buffers[dev->run->n_buffers]._next
# define HW(_next) RUN(hw_bufs)[RUN(n_bufs)]._next
LOG_DEBUG("Mapping device buffer %u ...", dev->run->n_buffers);
HW_BUFFER(data) = mmap(NULL, buf_info.length, PROT_READ|PROT_WRITE, MAP_SHARED, dev->run->fd, buf_info.m.offset);
if (HW_BUFFER(data) == MAP_FAILED) {
LOG_PERROR("Can't map device buffer %u", dev->run->n_buffers);
# ifdef WITH_OMX
HW(dma_fd) = -1;
HW(vcsm_handle) = -1;
# endif
A_MUTEX_INIT(&HW(grabbed_mutex));
LOG_DEBUG("Mapping device buffer %u ...", RUN(n_bufs));
if ((HW(raw.data) = mmap(
NULL,
buf_info.length,
PROT_READ | PROT_WRITE,
MAP_SHARED,
RUN(fd),
buf_info.m.offset
)) == MAP_FAILED) {
LOG_PERROR("Can't map device buffer %u", RUN(n_bufs));
return -1;
}
HW_BUFFER(allocated) = buf_info.length;
HW(raw.allocated) = buf_info.length;
# undef HW_BUFFER
# undef HW
}
return 0;
}
static int _device_open_io_method_userptr(struct device_t *dev) {
static int _device_open_io_method_userptr(device_s *dev) {
struct v4l2_requestbuffers req;
unsigned page_size = getpagesize();
unsigned buf_size = align_size(dev->run->raw_size, page_size);
MEMSET_ZERO(req);
req.count = dev->n_buffers;
req.count = dev->n_bufs;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_USERPTR;
LOG_DEBUG("Calling ioctl(VIDIOC_REQBUFS) for V4L2_MEMORY_USERPTR ...");
if (xioctl(dev->run->fd, VIDIOC_REQBUFS, &req) < 0) {
if (xioctl(RUN(fd), VIDIOC_REQBUFS, &req) < 0) {
LOG_PERROR("Device '%s' doesn't support V4L2_MEMORY_USERPTR", dev->path);
return -1;
}
@@ -622,39 +749,40 @@ static int _device_open_io_method_userptr(struct device_t *dev) {
LOG_ERROR("Insufficient buffer memory: %u", req.count);
return -1;
} else {
LOG_INFO("Requested %u HW buffers, got %u", dev->n_buffers, req.count);
LOG_INFO("Requested %u device buffers, got %u", dev->n_bufs, req.count);
}
LOG_DEBUG("Allocating HW buffers ...");
LOG_DEBUG("Allocating device buffers ...");
A_CALLOC(dev->run->hw_buffers, req.count);
for (dev->run->n_buffers = 0; dev->run->n_buffers < req.count; ++dev->run->n_buffers) {
# define HW_BUFFER(_next) dev->run->hw_buffers[dev->run->n_buffers]._next
A_CALLOC(RUN(hw_bufs), req.count);
assert(HW_BUFFER(data) = aligned_alloc(page_size, buf_size));
memset(HW_BUFFER(data), 0, buf_size);
HW_BUFFER(allocated) = buf_size;
const unsigned page_size = getpagesize();
const unsigned buf_size = align_size(RUN(raw_size), page_size);
# undef HW_BUFFER
for (RUN(n_bufs) = 0; RUN(n_bufs) < req.count; ++RUN(n_bufs)) {
# define HW(_next) RUN(hw_bufs)[RUN(n_bufs)]._next
assert(HW(raw.data) = aligned_alloc(page_size, buf_size));
memset(HW(raw.data), 0, buf_size);
HW(raw.allocated) = buf_size;
# undef HW
}
return 0;
}
static int _device_open_queue_buffers(struct device_t *dev) {
for (unsigned index = 0; index < dev->run->n_buffers; ++index) {
static int _device_open_queue_buffers(device_s *dev) {
for (unsigned index = 0; index < RUN(n_bufs); ++index) {
struct v4l2_buffer buf_info;
MEMSET_ZERO(buf_info);
buf_info.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf_info.memory = dev->io_method;
buf_info.index = index;
if (dev->io_method == V4L2_MEMORY_USERPTR) {
buf_info.m.userptr = (unsigned long)dev->run->hw_buffers[index].data;
buf_info.length = dev->run->hw_buffers[index].allocated;
buf_info.m.userptr = (unsigned long)RUN(hw_bufs)[index].raw.data;
buf_info.length = RUN(hw_bufs)[index].raw.allocated;
}
LOG_DEBUG("Calling ioctl(VIDIOC_QBUF) for buffer %u ...", index);
if (xioctl(dev->run->fd, VIDIOC_QBUF, &buf_info) < 0) {
if (xioctl(RUN(fd), VIDIOC_QBUF, &buf_info) < 0) {
LOG_PERROR("Can't VIDIOC_QBUF");
return -1;
}
@@ -662,20 +790,7 @@ static int _device_open_queue_buffers(struct device_t *dev) {
return 0;
}
static void _device_open_alloc_picbufs(struct device_t *dev) {
size_t picture_size = picture_get_generous_size(dev->run->width, dev->run->height);
LOG_DEBUG("Allocating picture buffers ...");
A_CALLOC(dev->run->pictures, dev->run->n_buffers);
for (unsigned index = 0; index < dev->run->n_buffers; ++index) {
dev->run->pictures[index] = picture_init();
LOG_DEBUG("Pre-allocating picture buffer %u sized %zu bytes... ", index, picture_size);
picture_realloc_data(dev->run->pictures[index], picture_size);
}
}
static int _device_apply_resolution(struct device_t *dev, unsigned width, unsigned height) {
static int _device_apply_resolution(device_s *dev, unsigned width, unsigned height) {
// Тут VIDEO_MIN_* не используются из-за странностей минимального разрешения при отсутствии сигнала
// у некоторых устройств, например Auvidea B101
if (
@@ -686,12 +801,12 @@ static int _device_apply_resolution(struct device_t *dev, unsigned width, unsign
width, height, VIDEO_MAX_WIDTH, VIDEO_MAX_HEIGHT);
return -1;
}
dev->run->width = width;
dev->run->height = height;
RUN(width) = width;
RUN(height) = height;
return 0;
}
static void _device_apply_controls(struct device_t *dev) {
static void _device_apply_controls(device_s *dev) {
# define SET_CID_VALUE(_cid, _field, _value, _quiet) { \
struct v4l2_queryctrl query; \
if (_device_query_control(dev, &query, #_field, _cid, _quiet) == 0) { \
@@ -736,6 +851,9 @@ static void _device_apply_controls(struct device_t *dev) {
CONTROL_MANUAL_CID ( V4L2_CID_BACKLIGHT_COMPENSATION, backlight_compensation);
CONTROL_AUTO_CID (V4L2_CID_AUTO_WHITE_BALANCE, V4L2_CID_WHITE_BALANCE_TEMPERATURE, white_balance);
CONTROL_AUTO_CID (V4L2_CID_AUTOGAIN, V4L2_CID_GAIN, gain);
CONTROL_MANUAL_CID ( V4L2_CID_COLORFX, color_effect);
CONTROL_MANUAL_CID ( V4L2_CID_VFLIP, flip_vertical);
CONTROL_MANUAL_CID ( V4L2_CID_HFLIP, flip_horizontal);
# undef CONTROL_AUTO_CID
# undef CONTROL_MANUAL_CID
@@ -743,12 +861,15 @@ static void _device_apply_controls(struct device_t *dev) {
# undef SET_CID_VALUE
}
static int _device_query_control(struct device_t *dev, struct v4l2_queryctrl *query, const char *name, unsigned cid, bool quiet) {
static int _device_query_control(
device_s *dev, struct v4l2_queryctrl *query,
const char *name, unsigned cid, bool quiet) {
// cppcheck-suppress redundantPointerOp
MEMSET_ZERO(*query);
query->id = cid;
if (xioctl(dev->run->fd, VIDIOC_QUERYCTRL, query) < 0 || query->flags & V4L2_CTRL_FLAG_DISABLED) {
if (xioctl(RUN(fd), VIDIOC_QUERYCTRL, query) < 0 || query->flags & V4L2_CTRL_FLAG_DISABLED) {
if (!quiet) {
LOG_ERROR("Changing control %s is unsupported", name);
}
@@ -757,8 +878,9 @@ static int _device_query_control(struct device_t *dev, struct v4l2_queryctrl *qu
return 0;
}
static void _device_set_control(struct device_t *dev, struct v4l2_queryctrl *query, const char *name, unsigned cid, int value, bool quiet) {
struct v4l2_control ctl;
static void _device_set_control(
device_s *dev, struct v4l2_queryctrl *query,
const char *name, unsigned cid, int value, bool quiet) {
if (value < query->minimum || value > query->maximum || value % query->step != 0) {
if (!quiet) {
@@ -768,11 +890,12 @@ static void _device_set_control(struct device_t *dev, struct v4l2_queryctrl *que
return;
}
struct v4l2_control ctl;
MEMSET_ZERO(ctl);
ctl.id = cid;
ctl.value = value;
if (xioctl(dev->run->fd, VIDIOC_S_CTRL, &ctl) < 0) {
if (xioctl(RUN(fd), VIDIOC_S_CTRL, &ctl) < 0) {
if (!quiet) {
LOG_PERROR("Can't set control %s", name);
}
@@ -781,23 +904,6 @@ static void _device_set_control(struct device_t *dev, struct v4l2_queryctrl *que
}
}
static const char *_format_to_string_fourcc(char *buf, size_t size, unsigned format) {
assert(size >= 8);
buf[0] = format & 0x7F;
buf[1] = (format >> 8) & 0x7F;
buf[2] = (format >> 16) & 0x7F;
buf[3] = (format >> 24) & 0x7F;
if (format & ((unsigned)1 << 31)) {
buf[4] = '-';
buf[5] = 'B';
buf[6] = 'E';
buf[7] = '\0';
} else {
buf[4] = '\0';
}
return buf;
}
static const char *_format_to_string_nullable(unsigned format) {
for (unsigned index = 0; index < ARRAY_LEN(_FORMATS); ++index) {
if (format == _FORMATS[index].format) {
@@ -829,3 +935,5 @@ static const char *_io_method_to_string_supported(enum v4l2_memory io_method) {
}
return "unsupported";
}
# undef RUN

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -22,12 +22,34 @@
#pragma once
#include <stdlib.h>
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <assert.h>
#include <sys/select.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <pthread.h>
#include <linux/videodev2.h>
#include <linux/v4l2-controls.h>
#ifdef WITH_OMX
# include <interface/vcsm/user-vcsm.h>
#endif
#include "picture.h"
#include "../libs/tools.h"
#include "../libs/logging.h"
#include "../libs/threading.h"
#include "../libs/frame.h"
#include "xioctl.h"
#define VIDEO_MIN_WIDTH ((unsigned)160)
@@ -48,85 +70,99 @@
#define IO_METHODS_STR "MMAP, USERPTR"
struct hw_buffer_t {
unsigned char *data;
size_t used;
size_t allocated;
struct v4l2_buffer buf_info;
};
typedef struct {
frame_s raw;
struct device_runtime_t {
int fd;
unsigned width;
unsigned height;
unsigned format;
size_t raw_size;
unsigned n_buffers;
unsigned n_workers;
struct hw_buffer_t *hw_buffers;
struct picture_t **pictures;
bool capturing;
};
struct v4l2_buffer buf_info;
enum control_mode_t {
# ifdef WITH_OMX
int dma_fd;
int vcsm_handle;
# endif
pthread_mutex_t grabbed_mutex;
bool grabbed;
} hw_buffer_s;
typedef struct {
int fd;
unsigned width;
unsigned height;
unsigned format;
unsigned stride;
unsigned hw_fps;
unsigned jpeg_quality;
size_t raw_size;
unsigned n_bufs;
hw_buffer_s *hw_bufs;
bool capturing;
bool persistent_timeout_reported;
} device_runtime_s;
typedef enum {
CTL_MODE_NONE = 0,
CTL_MODE_VALUE,
CTL_MODE_AUTO,
CTL_MODE_DEFAULT,
};
} control_mode_e;
struct control_t {
enum control_mode_t mode;
int value;
};
typedef struct {
control_mode_e mode;
int value;
} control_s;
struct controls_t {
struct control_t brightness;
struct control_t contrast;
struct control_t saturation;
struct control_t hue;
struct control_t gamma;
struct control_t sharpness;
struct control_t backlight_compensation;
struct control_t white_balance;
struct control_t gain;
};
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;
struct device_t {
char *path;
unsigned input;
unsigned width;
unsigned height;
unsigned format;
v4l2_std_id standard;
typedef struct {
char *path;
unsigned input;
unsigned width;
unsigned height;
unsigned format;
unsigned jpeg_quality;
v4l2_std_id standard;
enum v4l2_memory io_method;
bool dv_timings;
unsigned n_buffers;
unsigned n_workers;
unsigned desired_fps;
size_t min_frame_size;
bool persistent;
unsigned timeout;
unsigned error_delay;
bool dv_timings;
unsigned n_bufs;
unsigned desired_fps;
size_t min_frame_size;
bool persistent;
unsigned timeout;
struct controls_t ctl;
controls_s ctl;
struct device_runtime_t *run;
};
device_runtime_s *run;
} device_s;
struct device_t *device_init(void);
void device_destroy(struct device_t *dev);
device_s *device_init(void);
void device_destroy(device_s *dev);
int device_parse_format(const char *str);
v4l2_std_id device_parse_standard(const char *str);
int device_parse_io_method(const char *str);
int device_open(struct device_t *dev);
void device_close(struct device_t *dev);
int device_open(device_s *dev);
void device_close(device_s *dev);
int device_switch_capturing(struct device_t *dev, bool enable);
int device_select(struct device_t *dev, bool *has_read, bool *has_write, bool *has_error);
int device_grab_buffer(struct device_t *dev);
int device_release_buffer(struct device_t *dev, unsigned index);
int device_consume_event(struct device_t *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);

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

@@ -0,0 +1,295 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "encoder.h"
static const struct {
const char *name;
const encoder_type_e type;
} _ENCODER_TYPES[] = {
{"CPU", ENCODER_TYPE_CPU},
{"HW", ENCODER_TYPE_HW},
# ifdef WITH_OMX
{"OMX", ENCODER_TYPE_OMX},
# endif
{"NOOP", ENCODER_TYPE_NOOP},
};
static void *_worker_job_init(worker_s *wr, void *v_enc);
static void _worker_job_destroy(void *v_job);
static bool _worker_run_job(worker_s *wr);
#define ER(_next) enc->run->_next
encoder_s *encoder_init(void) {
encoder_runtime_s *run;
A_CALLOC(run, 1);
run->type = ENCODER_TYPE_CPU;
run->quality = 80;
A_MUTEX_INIT(&run->mutex);
encoder_s *enc;
A_CALLOC(enc, 1);
enc->type = run->type;
enc->n_workers = get_cores_available();
enc->run = run;
return enc;
}
void encoder_destroy(encoder_s *enc) {
# 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]));
}
}
free(ER(omxs));
}
# endif
A_MUTEX_DESTROY(&ER(mutex));
free(enc->run);
free(enc);
}
encoder_type_e encoder_parse_type(const char *str) {
for (unsigned index = 0; index < ARRAY_LEN(_ENCODER_TYPES); ++index) {
if (!strcasecmp(str, _ENCODER_TYPES[index].name)) {
return _ENCODER_TYPES[index].type;
}
}
return ENCODER_TYPE_UNKNOWN;
}
const char *encoder_type_to_string(encoder_type_e type) {
for (unsigned index = 0; index < ARRAY_LEN(_ENCODER_TYPES); ++index) {
if (_ENCODER_TYPES[index].type == type) {
return _ENCODER_TYPES[index].name;
}
}
return _ENCODER_TYPES[0].name;
}
workers_pool_s *encoder_workers_pool_init(encoder_s *enc, device_s *dev) {
# define DR(_next) dev->run->_next
encoder_type_e type = (ER(cpu_forced) ? ENCODER_TYPE_CPU : enc->type);
unsigned quality = dev->jpeg_quality;
unsigned n_workers = min_u(enc->n_workers, DR(n_bufs));
bool cpu_forced = false;
if (is_jpeg(DR(format)) && type != ENCODER_TYPE_HW) {
LOG_INFO("Switching to HW encoder: the input is (M)JPEG ...");
type = ENCODER_TYPE_HW;
}
if (type == ENCODER_TYPE_HW) {
if (!is_jpeg(DR(format))) {
LOG_INFO("Switching to CPU encoder: the input format is not (M)JPEG ...");
goto use_cpu;
}
quality = DR(jpeg_quality);
n_workers = 1;
}
# 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;
}
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;
}
}
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) {
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;
quality = dev->jpeg_quality;
ok:
if (type == ENCODER_TYPE_NOOP) {
LOG_INFO("Using JPEG NOOP encoder");
} else if (quality == 0) {
LOG_INFO("Using JPEG quality: encoder default");
} else {
LOG_INFO("Using JPEG quality: %u%%", quality);
}
A_MUTEX_LOCK(&ER(mutex));
ER(type) = type;
ER(quality) = quality;
if (cpu_forced) {
ER(cpu_forced) = true;
}
A_MUTEX_UNLOCK(&ER(mutex));
long double desired_interval = 0;
if (dev->desired_fps > 0 && (dev->desired_fps < dev->run->hw_fps || dev->run->hw_fps == 0)) {
desired_interval = (long double)1 / dev->desired_fps;
}
return workers_pool_init(
"JPEG", "jw", n_workers, desired_interval,
_worker_job_init, (void *)enc,
_worker_job_destroy,
_worker_run_job);
# undef DR
}
void encoder_get_runtime_params(encoder_s *enc, encoder_type_e *type, unsigned *quality) {
A_MUTEX_LOCK(&ER(mutex));
*type = ER(type);
*quality = ER(quality);
A_MUTEX_UNLOCK(&ER(mutex));
}
static void *_worker_job_init(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);
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);
free(job);
}
#undef ER
static bool _worker_run_job(worker_s *wr) {
encoder_job_s *job = (encoder_job_s *)wr->job;
frame_s *src = &job->hw->raw;
frame_s *dest = job->dest;
# define ER(_next) job->enc->run->_next
LOG_DEBUG("Worker %s compressing JPEG from buffer %u ...", wr->name, job->hw->buf_info.index);
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) {
goto error;
}
}
# endif
else if (ER(type) == ENCODER_TYPE_NOOP) {
LOG_VERBOSE("Compressing buffer using NOOP (do nothing)");
usleep(5000); // Просто чтобы работала логика desired_fps
}
dest->encode_end_ts = get_now_monotonic();
LOG_VERBOSE("Compressed new JPEG: size=%zu, time=%0.3Lf, worker=%s, buffer=%u",
job->dest->used,
job->dest->encode_end_ts - job->dest->encode_begin_ts,
wr->name,
job->hw->buf_info.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));
return false;
# endif
# undef ER
}

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 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,21 +22,28 @@
#pragma once
#include <stdlib.h>
#include <stdbool.h>
#include <strings.h>
#include <assert.h>
#include <pthread.h>
#include <linux/videodev2.h>
#include "../libs/tools.h"
#include "../libs/threading.h"
#include "../libs/logging.h"
#include "../libs/frame.h"
#include "device.h"
#include "workers.h"
#include "encoders/cpu/encoder.h"
#include "encoders/hw/encoder.h"
#ifdef WITH_OMX
# include "encoders/omx/encoder.h"
# define ENCODER_TYPES_OMX_HINT ", OMX"
# ifndef CFG_MAX_GLITCHED_RESOLUTIONS
# define CFG_MAX_GLITCHED_RESOLUTIONS 1024
# endif
# define MAX_GLITCHED_RESOLUTIONS ((unsigned)(CFG_MAX_GLITCHED_RESOLUTIONS))
#else
# define ENCODER_TYPES_OMX_HINT ""
#endif
@@ -44,46 +51,53 @@
#define ENCODER_TYPES_STR \
"CPU, HW" \
ENCODER_TYPES_OMX_HINT
ENCODER_TYPES_OMX_HINT \
", NOOP"
enum encoder_type_t {
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;
struct encoder_runtime_t {
enum encoder_type_t type;
unsigned quality;
bool cpu_forced;
pthread_mutex_t mutex;
typedef struct {
encoder_type_e type;
unsigned quality;
bool cpu_forced;
pthread_mutex_t mutex;
# ifdef WITH_OMX
unsigned n_omxs;
struct omx_encoder_t **omxs;
unsigned n_omxs;
omx_encoder_s **omxs;
# endif
};
} encoder_runtime_s;
struct encoder_t {
enum encoder_type_t type;
unsigned quality;
# ifdef WITH_OMX
unsigned n_glitched_resolutions;
unsigned glitched_resolutions[2][MAX_GLITCHED_RESOLUTIONS];
# endif
typedef struct {
encoder_type_e type;
unsigned n_workers;
struct encoder_runtime_t *run;
};
encoder_runtime_s *run;
} encoder_s;
typedef struct {
encoder_s *enc;
hw_buffer_s *hw;
char *dest_role;
frame_s *dest;
} encoder_job_s;
struct encoder_t *encoder_init(void);
void encoder_destroy(struct encoder_t *encoder);
encoder_s *encoder_init(void);
void encoder_destroy(encoder_s *enc);
enum encoder_type_t encoder_parse_type(const char *str);
const char *encoder_type_to_string(enum encoder_type_t type);
encoder_type_e encoder_parse_type(const char *str);
const char *encoder_type_to_string(encoder_type_e type);
void encoder_prepare(struct encoder_t *encoder, struct device_t *dev);
int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, unsigned worker_number, unsigned buf_index);
workers_pool_s *encoder_workers_pool_init(encoder_s *enc, device_s *dev);
void encoder_get_runtime_params(encoder_s *enc, encoder_type_e *type, unsigned *quality);
int encoder_compress(encoder_s *enc, unsigned worker_number, frame_s *src, frame_s *dest);

View File

@@ -7,7 +7,7 @@
# Copyright (C) 2005-2006 Laurent Pinchart & Michel Xhaard #
# Copyright (C) 2006 Gabriel A. Devenyi #
# Copyright (C) 2007 Tom Stöveken #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 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,50 +27,27 @@
#include "encoder.h"
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <jpeglib.h>
#include <linux/videodev2.h>
#include "../../tools.h"
#include "../../picture.h"
#include "../../device.h"
typedef struct {
struct jpeg_destination_mgr mgr; // Default manager
JOCTET *buf; // Start of buffer
frame_s *frame;
} _jpeg_dest_manager_s;
struct _jpeg_dest_manager_t {
struct jpeg_destination_mgr mgr; // Default manager
JOCTET *buffer; // Start of buffer
struct picture_t *picture;
};
static void _jpeg_set_dest_frame(j_compress_ptr jpeg, frame_s *frame);
static void _jpeg_set_picture(j_compress_ptr jpeg, struct picture_t *picture);
static void _jpeg_write_scanlines_yuyv(
struct jpeg_compress_struct *jpeg, const unsigned char *data,
unsigned width, unsigned height);
static void _jpeg_write_scanlines_uyvy(
struct jpeg_compress_struct *jpeg, const unsigned char *data,
unsigned width, unsigned height);
static void _jpeg_write_scanlines_rgb565(
struct jpeg_compress_struct *jpeg, const unsigned char *data,
unsigned width, unsigned height);
static void _jpeg_write_scanlines_rgb24(
struct jpeg_compress_struct *jpeg, const unsigned char *data,
unsigned width, unsigned height);
static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg, const frame_s *frame);
static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg, const frame_s *frame);
static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, const frame_s *frame);
static void _jpeg_write_scanlines_rgb24(struct jpeg_compress_struct *jpeg, const frame_s *frame);
static void _jpeg_init_destination(j_compress_ptr jpeg);
static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg);
static void _jpeg_term_destination(j_compress_ptr jpeg);
void cpu_encoder_compress_buffer(struct device_t *dev, unsigned index, unsigned quality) {
void cpu_encoder_compress(const frame_s *src, frame_s *dest, unsigned quality) {
// This function based on compress_image_to_jpeg() from mjpg-streamer
struct jpeg_compress_struct jpeg;
@@ -79,10 +56,10 @@ void cpu_encoder_compress_buffer(struct device_t *dev, unsigned index, unsigned
jpeg.err = jpeg_std_error(&jpeg_error);
jpeg_create_compress(&jpeg);
_jpeg_set_picture(&jpeg, dev->run->pictures[index]);
_jpeg_set_dest_frame(&jpeg, dest);
jpeg.image_width = dev->run->width;
jpeg.image_height = dev->run->height;
jpeg.image_width = src->width;
jpeg.image_height = src->height;
jpeg.input_components = 3;
jpeg.in_color_space = JCS_RGB;
@@ -92,9 +69,9 @@ void cpu_encoder_compress_buffer(struct device_t *dev, unsigned index, unsigned
jpeg_start_compress(&jpeg, TRUE);
# define WRITE_SCANLINES(_format, _func) \
case _format: { _func(&jpeg, dev->run->hw_buffers[index].data, dev->run->width, dev->run->height); break; }
case _format: { _func(&jpeg, src); break; }
switch (dev->run->format) {
switch (src->format) {
// https://www.fourcc.org/yuv.php
WRITE_SCANLINES(V4L2_PIX_FMT_YUYV, _jpeg_write_scanlines_yuyv);
WRITE_SCANLINES(V4L2_PIX_FMT_UYVY, _jpeg_write_scanlines_uyvy);
@@ -108,25 +85,23 @@ void cpu_encoder_compress_buffer(struct device_t *dev, unsigned index, unsigned
jpeg_finish_compress(&jpeg);
jpeg_destroy_compress(&jpeg);
assert(dev->run->pictures[index]->used > 0);
assert(dest->used > 0);
}
static void _jpeg_set_picture(j_compress_ptr jpeg, struct picture_t *picture) {
struct _jpeg_dest_manager_t *dest;
static void _jpeg_set_dest_frame(j_compress_ptr jpeg, frame_s *frame) {
if (jpeg->dest == NULL) {
assert((jpeg->dest = (struct jpeg_destination_mgr *)(*jpeg->mem->alloc_small)(
(j_common_ptr) jpeg, JPOOL_PERMANENT, sizeof(struct _jpeg_dest_manager_t)
(j_common_ptr) jpeg, JPOOL_PERMANENT, sizeof(_jpeg_dest_manager_s)
)));
}
dest = (struct _jpeg_dest_manager_t *)jpeg->dest;
_jpeg_dest_manager_s *dest = (_jpeg_dest_manager_s *)jpeg->dest;
dest->mgr.init_destination = _jpeg_init_destination;
dest->mgr.empty_output_buffer = _jpeg_empty_output_buffer;
dest->mgr.term_destination = _jpeg_term_destination;
dest->picture = picture;
dest->frame = frame;
picture->used = 0;
frame->used = 0;
}
#define YUV_R(_y, _, _v) (((_y) + (359 * (_v))) >> 8)
@@ -134,20 +109,18 @@ static void _jpeg_set_picture(j_compress_ptr jpeg, struct picture_t *picture) {
#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 unsigned char *data,
unsigned width, unsigned height) {
static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg, const frame_s *frame) {
uint8_t *line_buf;
A_CALLOC(line_buf, frame->width * 3);
unsigned char *line_buffer;
JSAMPROW scanlines[1];
const unsigned padding = frame_get_padding(frame);
const uint8_t *data = frame->data;
unsigned z = 0;
A_CALLOC(line_buffer, width * 3);
while (jpeg->next_scanline < frame->height) {
uint8_t *ptr = line_buf;
while (jpeg->next_scanline < height) {
unsigned char *ptr = line_buffer;
for (unsigned x = 0; x < width; ++x) {
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;
@@ -165,28 +138,27 @@ static void _jpeg_write_scanlines_yuyv(
data += 4;
}
}
data += padding;
scanlines[0] = line_buffer;
JSAMPROW scanlines[1] = {line_buf};
jpeg_write_scanlines(jpeg, scanlines, 1);
}
free(line_buffer);
free(line_buf);
}
static void _jpeg_write_scanlines_uyvy(
struct jpeg_compress_struct *jpeg, const unsigned char *data,
unsigned width, unsigned height) {
static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg, const frame_s *frame) {
uint8_t *line_buf;
A_CALLOC(line_buf, frame->width * 3);
unsigned char *line_buffer;
JSAMPROW scanlines[1];
const unsigned padding = frame_get_padding(frame);
const uint8_t *data = frame->data;
unsigned z = 0;
A_CALLOC(line_buffer, width * 3);
while (jpeg->next_scanline < frame->height) {
uint8_t *ptr = line_buf;
while (jpeg->next_scanline < height) {
unsigned char *ptr = line_buffer;
for(unsigned x = 0; x < width; ++x) {
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;
@@ -204,12 +176,13 @@ static void _jpeg_write_scanlines_uyvy(
data += 4;
}
}
data += padding;
scanlines[0] = line_buffer;
JSAMPROW scanlines[1] = {line_buf};
jpeg_write_scanlines(jpeg, scanlines, 1);
}
free(line_buffer);
free(line_buf);
}
#undef NORM_COMPONENT
@@ -217,69 +190,68 @@ static void _jpeg_write_scanlines_uyvy(
#undef YUV_G
#undef YUV_R
static void _jpeg_write_scanlines_rgb565(
struct jpeg_compress_struct *jpeg, const unsigned char *data,
unsigned width, unsigned height) {
static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, const frame_s *frame) {
uint8_t *line_buf;
A_CALLOC(line_buf, frame->width * 3);
unsigned char *line_buffer;
JSAMPROW scanlines[1];
const unsigned padding = frame_get_padding(frame);
const uint8_t *data = frame->data;
A_CALLOC(line_buffer, width * 3);
while (jpeg->next_scanline < frame->height) {
uint8_t *ptr = line_buf;
while (jpeg->next_scanline < height) {
unsigned char *ptr = line_buffer;
for(unsigned x = 0; x < width; ++x) {
for (unsigned x = 0; x < frame->width; ++x) {
unsigned int two_byte = (data[1] << 8) + data[0];
*(ptr++) = data[1] & 248; // Red
*(ptr++) = (unsigned char)((two_byte & 2016) >> 3); // Green
*(ptr++) = (uint8_t)((two_byte & 2016) >> 3); // Green
*(ptr++) = (data[0] & 31) * 8; // Blue
data += 2;
}
data += padding;
scanlines[0] = line_buffer;
JSAMPROW scanlines[1] = {line_buf};
jpeg_write_scanlines(jpeg, scanlines, 1);
}
free(line_buffer);
free(line_buf);
}
static void _jpeg_write_scanlines_rgb24(
struct jpeg_compress_struct *jpeg, const unsigned char *data,
unsigned width, unsigned height) {
static void _jpeg_write_scanlines_rgb24(struct jpeg_compress_struct *jpeg, const frame_s *frame) {
const unsigned padding = frame_get_padding(frame);
uint8_t *data = frame->data;
JSAMPROW scanlines[1];
while (jpeg->next_scanline < height) {
scanlines[0] = (unsigned char *)(data + jpeg->next_scanline * width * 3);
while (jpeg->next_scanline < frame->height) {
JSAMPROW scanlines[1] = {data};
jpeg_write_scanlines(jpeg, scanlines, 1);
data += (jpeg->next_scanline * frame->width * 3) + padding;
}
}
#define JPEG_OUTPUT_BUFFER_SIZE ((size_t)4096)
static void _jpeg_init_destination(j_compress_ptr jpeg) {
struct _jpeg_dest_manager_t *dest = (struct _jpeg_dest_manager_t *)jpeg->dest;
_jpeg_dest_manager_s *dest = (_jpeg_dest_manager_s *)jpeg->dest;
// Allocate the output buffer - it will be released when done with image
assert((dest->buffer = (JOCTET *)(*jpeg->mem->alloc_small)(
assert((dest->buf = (JOCTET *)(*jpeg->mem->alloc_small)(
(j_common_ptr) jpeg, JPOOL_IMAGE, JPEG_OUTPUT_BUFFER_SIZE * sizeof(JOCTET)
)));
dest->mgr.next_output_byte = dest->buffer;
dest->mgr.next_output_byte = dest->buf;
dest->mgr.free_in_buffer = JPEG_OUTPUT_BUFFER_SIZE;
}
static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg) {
// Called whenever local jpeg buffer fills up
struct _jpeg_dest_manager_t *dest = (struct _jpeg_dest_manager_t *)jpeg->dest;
_jpeg_dest_manager_s *dest = (_jpeg_dest_manager_s *)jpeg->dest;
picture_append_data(dest->picture, dest->buffer, JPEG_OUTPUT_BUFFER_SIZE);
frame_append_data(dest->frame, dest->buf, JPEG_OUTPUT_BUFFER_SIZE);
dest->mgr.next_output_byte = dest->buffer;
dest->mgr.next_output_byte = dest->buf;
dest->mgr.free_in_buffer = JPEG_OUTPUT_BUFFER_SIZE;
return TRUE;
@@ -289,11 +261,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.
struct _jpeg_dest_manager_t *dest = (struct _jpeg_dest_manager_t *)jpeg->dest;
_jpeg_dest_manager_s *dest = (_jpeg_dest_manager_s *)jpeg->dest;
size_t final = JPEG_OUTPUT_BUFFER_SIZE - dest->mgr.free_in_buffer;
// Write any data remaining in the buffer.
picture_append_data(dest->picture, dest->buffer, final);
frame_append_data(dest->frame, dest->buf, final);
}
#undef JPEG_OUTPUT_BUFFER_SIZE

View File

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

View File

@@ -7,7 +7,7 @@
# Copyright (C) 2005-2006 Laurent Pinchart & Michel Xhaard #
# Copyright (C) 2006 Gabriel A. Devenyi #
# Copyright (C) 2007 Tom Stöveken #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 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,54 +27,20 @@
#include "encoder.h"
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include <linux/videodev2.h>
#include "../../tools.h"
#include "../../logging.h"
#include "../../xioctl.h"
#include "../../picture.h"
#include "../../device.h"
#include "huffman.h"
void _copy_plus_huffman(const frame_s *src, frame_s *dest);
static bool _is_huffman(const uint8_t *data);
void _copy_plus_huffman(const struct hw_buffer_t *src, struct picture_t *dest);
static bool _is_huffman(const unsigned char *data);
int hw_encoder_prepare(struct device_t *dev, unsigned quality) {
struct v4l2_jpegcompression comp;
MEMSET_ZERO(comp);
if (xioctl(dev->run->fd, VIDIOC_G_JPEGCOMP, &comp) < 0) {
LOG_ERROR("Device does not support setting of HW encoding quality parameters");
return -1;
}
comp.quality = quality;
if (xioctl(dev->run->fd, VIDIOC_S_JPEGCOMP, &comp) < 0) {
LOG_ERROR("Unable to change MJPG quality for JPEG source with HW pass-through encoder");
return -1;
}
return 0;
void hw_encoder_compress(const frame_s *src, frame_s *dest) {
assert(is_jpeg(src->format));
_copy_plus_huffman(src, dest);
}
void hw_encoder_compress_buffer(struct device_t *dev, unsigned index) {
if (dev->run->format != V4L2_PIX_FMT_MJPEG && dev->run->format != V4L2_PIX_FMT_JPEG) {
assert(0 && "Unsupported input format for HW encoder");
}
_copy_plus_huffman(&dev->run->hw_buffers[index], dev->run->pictures[index]);
}
void _copy_plus_huffman(const struct hw_buffer_t *src, struct picture_t *dest) {
void _copy_plus_huffman(const frame_s *src, frame_s *dest) {
if (!_is_huffman(src->data)) {
const unsigned char *src_ptr = src->data;
const unsigned char *src_end = src->data + src->used;
size_t paste;
const uint8_t *src_ptr = src->data;
const uint8_t *src_end = src->data + src->used;
while ((((src_ptr[0] << 8) | src_ptr[1]) != 0xFFC0) && (src_ptr < src_end)) {
src_ptr += 1;
@@ -83,24 +49,25 @@ void _copy_plus_huffman(const struct hw_buffer_t *src, struct picture_t *dest) {
dest->used = 0; // Error
return;
}
paste = src_ptr - src->data;
picture_set_data(dest, src->data, paste);
picture_append_data(dest, HUFFMAN_TABLE, sizeof(HUFFMAN_TABLE));
picture_append_data(dest, src_ptr, src->used - paste);
const size_t paste = src_ptr - src->data;
frame_set_data(dest, src->data, paste);
frame_append_data(dest, HUFFMAN_TABLE, sizeof(HUFFMAN_TABLE));
frame_append_data(dest, src_ptr, src->used - paste);
} else {
picture_set_data(dest, src->data, src->used);
frame_set_data(dest, src->data, src->used);
}
}
static bool _is_huffman(const unsigned char *data) {
static bool _is_huffman(const uint8_t *data) {
unsigned count = 0;
while (((data[0] << 8) | data[1]) != 0xFFDA) {
while ((((uint16_t)data[0] << 8) | data[1]) != 0xFFDA) {
if (count++ > 2048) {
return false;
}
if (((data[0] << 8) | data[1]) == 0xFFC4) {
if ((((uint16_t)data[0] << 8) | data[1]) == 0xFFC4) {
return true;
}
data += 1;

View File

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

View File

@@ -7,7 +7,7 @@
# Copyright (C) 2005-2006 Laurent Pinchart & Michel Xhaard #
# Copyright (C) 2006 Gabriel A. Devenyi #
# Copyright (C) 2007 Tom Stöveken #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 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,7 +27,10 @@
#pragma once
static const unsigned char HUFFMAN_TABLE[] = {
#include <stdint.h>
static const uint8_t HUFFMAN_TABLE[] = {
0xFF, 0xC4, 0x01, 0xA2, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02,
0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x01, 0x00, 0x03,

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 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,43 +22,34 @@
#include "component.h"
#include <unistd.h>
#include <IL/OMX_Core.h>
#include <IL/OMX_Component.h>
#include "../../logging.h"
#include "formatters.h"
static int _omx_component_wait_port_changed(OMX_HANDLETYPE *comp, OMX_U32 port, OMX_BOOL enabled);
static int _omx_component_wait_state_changed(OMX_HANDLETYPE *comp, OMX_STATETYPE wanted);
static int _component_wait_port_changed(OMX_HANDLETYPE *component, OMX_U32 port, OMX_BOOL enabled);
static int _component_wait_state_changed(OMX_HANDLETYPE *component, OMX_STATETYPE wanted);
int component_enable_port(OMX_HANDLETYPE *component, OMX_U32 port) {
int omx_component_enable_port(OMX_HANDLETYPE *comp, OMX_U32 port) {
OMX_ERRORTYPE error;
LOG_DEBUG("Enabling OMX port %u ...", port);
if ((error = OMX_SendCommand(*component, OMX_CommandPortEnable, port, NULL)) != OMX_ErrorNone) {
if ((error = OMX_SendCommand(*comp, OMX_CommandPortEnable, port, NULL)) != OMX_ErrorNone) {
LOG_ERROR_OMX(error, "Can't enable OMX port %u", port);
return -1;
}
return _component_wait_port_changed(component, port, OMX_TRUE);
return _omx_component_wait_port_changed(comp, port, OMX_TRUE);
}
int component_disable_port(OMX_HANDLETYPE *component, OMX_U32 port) {
int omx_component_disable_port(OMX_HANDLETYPE *comp, OMX_U32 port) {
OMX_ERRORTYPE error;
LOG_DEBUG("Disabling OMX port %u ...", port);
if ((error = OMX_SendCommand(*component, OMX_CommandPortDisable, port, NULL)) != OMX_ErrorNone) {
if ((error = OMX_SendCommand(*comp, OMX_CommandPortDisable, port, NULL)) != OMX_ErrorNone) {
LOG_ERROR_OMX(error, "Can't disable OMX port %u", port);
return -1;
}
return _component_wait_port_changed(component, port, OMX_FALSE);
return _omx_component_wait_port_changed(comp, port, OMX_FALSE);
}
int component_get_portdef(OMX_HANDLETYPE *component, OMX_PARAM_PORTDEFINITIONTYPE *portdef, OMX_U32 port) {
int omx_component_get_portdef(OMX_HANDLETYPE *comp, OMX_PARAM_PORTDEFINITIONTYPE *portdef, OMX_U32 port) {
OMX_ERRORTYPE error;
// cppcheck-suppress redundantPointerOp
@@ -66,38 +57,43 @@ int component_get_portdef(OMX_HANDLETYPE *component, OMX_PARAM_PORTDEFINITIONTYP
portdef->nPortIndex = port;
LOG_DEBUG("Fetching OMX port %u definition ...", port);
if ((error = OMX_GetParameter(*component, OMX_IndexParamPortDefinition, portdef)) != OMX_ErrorNone) {
if ((error = OMX_GetParameter(*comp, OMX_IndexParamPortDefinition, portdef)) != OMX_ErrorNone) {
LOG_ERROR_OMX(error, "Can't get OMX port %u definition", port);
return -1;
}
return 0;
}
int component_set_portdef(OMX_HANDLETYPE *component, OMX_PARAM_PORTDEFINITIONTYPE *portdef) {
int omx_component_set_portdef(OMX_HANDLETYPE *comp, OMX_PARAM_PORTDEFINITIONTYPE *portdef) {
OMX_ERRORTYPE error;
LOG_DEBUG("Writing OMX port %u definition ...", portdef->nPortIndex);
if ((error = OMX_SetParameter(*component, OMX_IndexParamPortDefinition, portdef)) != OMX_ErrorNone) {
if ((error = OMX_SetParameter(*comp, OMX_IndexParamPortDefinition, portdef)) != OMX_ErrorNone) {
LOG_ERROR_OMX(error, "Can't set OMX port %u definition", portdef->nPortIndex);
return -1;
}
return 0;
}
int component_set_state(OMX_HANDLETYPE *component, OMX_STATETYPE state) {
int omx_component_set_state(OMX_HANDLETYPE *comp, OMX_STATETYPE state) {
const char *state_str = omx_state_to_string(state);
OMX_ERRORTYPE error;
int retries = 50;
LOG_DEBUG("Switching component state to %s ...", state_str);
int retries = 50;
do {
error = OMX_SendCommand(*component, OMX_CommandStateSet, state, NULL);
error = OMX_SendCommand(*comp, OMX_CommandStateSet, state, NULL);
if (error == OMX_ErrorNone) {
return _component_wait_state_changed(component, state);
return _omx_component_wait_state_changed(comp, state);
} else if (error == OMX_ErrorInsufficientResources && retries) {
// Иногда железо не инициализируется, хз почему, просто ретраим, со второй попытки сработает
LOG_ERROR_OMX(error, "Can't switch OMX component state to %s, need to retry", state_str);
if (retries > 45) {
LOG_VERBOSE("Can't switch OMX component state to %s, need to retry: %s",
state_str, omx_error_to_string(error));
} else {
LOG_ERROR_OMX(error, "Can't switch OMX component state to %s, need to retry", state_str);
}
retries -= 1;
usleep(8000);
} else {
@@ -110,16 +106,16 @@ int component_set_state(OMX_HANDLETYPE *component, OMX_STATETYPE state) {
}
static int _component_wait_port_changed(OMX_HANDLETYPE *component, OMX_U32 port, OMX_BOOL enabled) {
static int _omx_component_wait_port_changed(OMX_HANDLETYPE *comp, OMX_U32 port, OMX_BOOL enabled) {
OMX_ERRORTYPE error;
OMX_PARAM_PORTDEFINITIONTYPE portdef;
int retries = 50;
OMX_PARAM_PORTDEFINITIONTYPE portdef;
OMX_INIT_STRUCTURE(portdef);
portdef.nPortIndex = port;
int retries = 50;
do {
if ((error = OMX_GetParameter(*component, OMX_IndexParamPortDefinition, &portdef)) != OMX_ErrorNone) {
if ((error = OMX_GetParameter(*comp, OMX_IndexParamPortDefinition, &portdef)) != OMX_ErrorNone) {
LOG_ERROR_OMX(error, "Can't get OMX port %u definition for waiting", port);
return -1;
}
@@ -135,13 +131,13 @@ static int _component_wait_port_changed(OMX_HANDLETYPE *component, OMX_U32 port,
return (portdef.bEnabled == enabled ? 0 : -1);
}
static int _component_wait_state_changed(OMX_HANDLETYPE *component, OMX_STATETYPE wanted) {
static int _omx_component_wait_state_changed(OMX_HANDLETYPE *comp, OMX_STATETYPE wanted) {
OMX_ERRORTYPE error;
OMX_STATETYPE state;
int retries = 50;
int retries = 50;
do {
if ((error = OMX_GetState(*component, &state)) != OMX_ErrorNone) {
if ((error = OMX_GetState(*comp, &state)) != OMX_ErrorNone) {
LOG_ERROR_OMX(error, "Failed to get OMX component state");
return -1;
}

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 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,10 +23,15 @@
#pragma once
#include <string.h>
#include <unistd.h>
#include <IL/OMX_Core.h>
#include <IL/OMX_Component.h>
#include "../../../libs/logging.h"
#include "formatters.h"
#define OMX_INIT_STRUCTURE(_var) { \
memset(&(_var), 0, sizeof(_var)); \
@@ -39,10 +44,10 @@
}
int component_enable_port(OMX_HANDLETYPE *component, OMX_U32 port);
int component_disable_port(OMX_HANDLETYPE *component, OMX_U32 port);
int omx_component_enable_port(OMX_HANDLETYPE *comp, OMX_U32 port);
int omx_component_disable_port(OMX_HANDLETYPE *comp, OMX_U32 port);
int component_get_portdef(OMX_HANDLETYPE *component, OMX_PARAM_PORTDEFINITIONTYPE *portdef, OMX_U32 port);
int component_set_portdef(OMX_HANDLETYPE *component, OMX_PARAM_PORTDEFINITIONTYPE *portdef);
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 component_set_state(OMX_HANDLETYPE *component, OMX_STATETYPE state);
int omx_component_set_state(OMX_HANDLETYPE *comp, OMX_STATETYPE state);

View File

@@ -0,0 +1,481 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "encoder.h"
static const OMX_U32 _INPUT_PORT = 340;
static const OMX_U32 _OUTPUT_PORT = 341;
static int _vcos_semwait(VCOS_SEMAPHORE_T *sem);
static int _omx_init_component(omx_encoder_s *omx);
static int _omx_init_disable_ports(omx_encoder_s *omx);
static int _omx_setup_input(omx_encoder_s *omx, const frame_s *frame);
static int _omx_setup_output(omx_encoder_s *omx, unsigned quality);
static int _omx_encoder_clear_ports(omx_encoder_s *omx);
static OMX_ERRORTYPE _omx_event_handler(
UNUSED OMX_HANDLETYPE comp,
OMX_PTR v_omx, OMX_EVENTTYPE event, OMX_U32 data1,
UNUSED OMX_U32 data2, UNUSED OMX_PTR event_data);
static OMX_ERRORTYPE _omx_input_required_handler(
UNUSED OMX_HANDLETYPE comp,
OMX_PTR v_omx, UNUSED OMX_BUFFERHEADERTYPE *buf);
static OMX_ERRORTYPE _omx_output_available_handler(
UNUSED OMX_HANDLETYPE comp,
OMX_PTR v_omx, UNUSED OMX_BUFFERHEADERTYPE *buf);
omx_encoder_s *omx_encoder_init(void) {
// Some theory:
// - http://www.fourcc.org/yuv.php
// - https://kwasi-ich.de/blog/2017/11/26/omx/
// - https://github.com/hopkinskong/rpi-omx-jpeg-encode/blob/master/jpeg_bench.cpp
// - https://github.com/kwasmich/OMXPlayground/blob/master/omxJPEGEnc.c
// - https://github.com/gagle/raspberrypi-openmax-jpeg/blob/master/jpeg.c
// - https://www.raspberrypi.org/forums/viewtopic.php?t=154790
// - https://bitbucket.org/bensch128/omxjpegencode/src/master/jpeg_encoder.cpp
// - http://home.nouwen.name/RaspberryPi/documentation/ilcomponents/image_encode.html
LOG_INFO("Initializing OMX encoder ...");
omx_encoder_s *omx;
A_CALLOC(omx, 1);
if (vcos_semaphore_create(&omx->handler_sem, "handler_sem", 0) != VCOS_SUCCESS) {
LOG_ERROR("Can't create VCOS semaphore");
goto error;
}
omx->i_handler_sem = true;
if (_omx_init_component(omx) < 0) {
goto error;
}
if (_omx_init_disable_ports(omx) < 0) {
goto error;
}
return omx;
error:
omx_encoder_destroy(omx);
return NULL;
}
void omx_encoder_destroy(omx_encoder_s *omx) {
LOG_INFO("Destroying OMX encoder ...");
omx_component_set_state(&omx->comp, OMX_StateIdle);
_omx_encoder_clear_ports(omx);
omx_component_set_state(&omx->comp, OMX_StateLoaded);
if (omx->i_handler_sem) {
vcos_semaphore_delete(&omx->handler_sem);
}
if (omx->i_encoder) {
OMX_ERRORTYPE error;
if ((error = OMX_FreeHandle(omx->comp)) != OMX_ErrorNone) {
LOG_ERROR_OMX(error, "Can't free OMX.broadcom.image_encode");
}
}
free(omx);
}
int omx_encoder_prepare(omx_encoder_s *omx, const frame_s *frame, unsigned quality) {
if (align_size(frame->width, 32) != frame->width && frame_get_padding(frame) == 0) {
LOG_ERROR("%u %u", frame->width, frame->stride);
LOG_ERROR("OMX encoder can't handle unaligned width");
return -2;
}
if (omx_component_set_state(&omx->comp, OMX_StateIdle) < 0) {
return -1;
}
if (_omx_encoder_clear_ports(omx) < 0) {
return -1;
}
if (_omx_setup_input(omx, frame) < 0) {
return -1;
}
if (_omx_setup_output(omx, quality) < 0) {
return -1;
}
if (omx_component_set_state(&omx->comp, OMX_StateExecuting) < 0) {
return -1;
}
return 0;
}
int omx_encoder_compress(omx_encoder_s *omx, const frame_s *src, frame_s *dest) {
# define IN(_next) omx->input_buf->_next
# define OUT(_next) omx->output_buf->_next
OMX_ERRORTYPE error;
if ((error = OMX_FillThisBuffer(omx->comp, omx->output_buf)) != OMX_ErrorNone) {
LOG_ERROR_OMX(error, "Failed to request filling of the output buffer on encoder");
return -1;
}
dest->width = align_size(src->width, 32);
dest->used = 0;
omx->output_available = false;
omx->input_required = true;
size_t slice_size = (IN(nAllocLen) < src->used ? IN(nAllocLen) : src->used);
size_t pos = 0;
while (true) {
if (omx->failed) {
return -1;
}
if (omx->output_available) {
omx->output_available = false;
frame_append_data(dest, OUT(pBuffer) + OUT(nOffset), OUT(nFilledLen));
if (OUT(nFlags) & OMX_BUFFERFLAG_ENDOFFRAME) {
OUT(nFlags) = 0;
break;
}
if ((error = OMX_FillThisBuffer(omx->comp, omx->output_buf)) != OMX_ErrorNone) {
LOG_ERROR_OMX(error, "Failed to request filling of the output buffer on encoder");
return -1;
}
}
if (omx->input_required) {
omx->input_required = false;
if (pos == src->used) {
continue;
}
memcpy(IN(pBuffer), src->data + pos, slice_size);
IN(nOffset) = 0;
IN(nFilledLen) = slice_size;
pos += slice_size;
if (pos + slice_size > src->used) {
slice_size = src->used - pos;
}
if ((error = OMX_EmptyThisBuffer(omx->comp, omx->input_buf)) != OMX_ErrorNone) {
LOG_ERROR_OMX(error, "Failed to request emptying of the input buffer on encoder");
return -1;
}
}
if (_vcos_semwait(&omx->handler_sem) != 0) {
return -1;
}
}
# undef OUT
# undef IN
return 0;
}
static int _vcos_semwait(VCOS_SEMAPHORE_T *sem) {
// vcos_semaphore_wait() can wait infinite
// vcos_semaphore_wait_timeout() is broken by design:
// - https://github.com/pikvm/ustreamer/issues/56
// - https://github.com/raspberrypi/userland/issues/658
// CFG_OMX_SEMWAIT_TIMEOUT is ugly busyloop
// Три стула.
# ifdef CFG_OMX_SEMWAIT_TIMEOUT
long double deadline_ts = get_now_monotonic() + (long double)CFG_OMX_SEMWAIT_TIMEOUT; // Seconds
VCOS_STATUS_T sem_status;
while (true) {
sem_status = vcos_semaphore_trywait(sem);
if (sem_status == VCOS_SUCCESS) {
return 0;
} else if (sem_status != VCOS_EAGAIN || get_now_monotonic() > deadline_ts) {
break;
}
if (usleep(1000) < 0) {
break;
}
}
switch (sem_status) {
case VCOS_EAGAIN: LOG_ERROR("Can't wait VCOS semaphore: EAGAIN (timeout)"); break;
case VCOS_EINVAL: LOG_ERROR("Can't wait VCOS semaphore: EINVAL"); break;
default: LOG_ERROR("Can't wait VCOS semaphore: %d", sem_status); break;
}
return -1;
# else
return (vcos_semaphore_wait(sem) == VCOS_SUCCESS ? 0 : -1);
# endif
}
static int _omx_init_component(omx_encoder_s *omx) {
OMX_ERRORTYPE error;
OMX_CALLBACKTYPE callbacks;
MEMSET_ZERO(callbacks);
callbacks.EventHandler = _omx_event_handler;
callbacks.EmptyBufferDone = _omx_input_required_handler;
callbacks.FillBufferDone = _omx_output_available_handler;
LOG_DEBUG("Initializing OMX.broadcom.image_encode ...");
if ((error = OMX_GetHandle(&omx->comp, "OMX.broadcom.image_encode", omx, &callbacks)) != OMX_ErrorNone) {
LOG_ERROR_OMX(error, "Can't initialize OMX.broadcom.image_encode");
return -1;
}
omx->i_encoder = true;
return 0;
}
static int _omx_init_disable_ports(omx_encoder_s *omx) {
OMX_ERRORTYPE error;
OMX_INDEXTYPE types[] = {
OMX_IndexParamAudioInit, OMX_IndexParamVideoInit,
OMX_IndexParamImageInit, OMX_IndexParamOtherInit,
};
OMX_PORT_PARAM_TYPE ports;
OMX_INIT_STRUCTURE(ports);
if ((error = OMX_GetParameter(omx->comp, OMX_IndexParamImageInit, &ports)) != OMX_ErrorNone) {
LOG_ERROR_OMX(error, "Can't OMX_GetParameter(OMX_IndexParamImageInit)");
return -1;
}
for (unsigned index = 0; index < 4; ++index) {
if ((error = OMX_GetParameter(omx->comp, types[index], &ports)) != OMX_ErrorNone) {
LOG_ERROR_OMX(error, "Can't OMX_GetParameter(types[%u])", index);
return -1;
}
for (OMX_U32 port = ports.nStartPortNumber; port < ports.nStartPortNumber + ports.nPorts; ++port) {
if (omx_component_disable_port(&omx->comp, port) < 0) {
return -1;
}
}
}
return 0;
}
static int _omx_setup_input(omx_encoder_s *omx, const frame_s *frame) {
LOG_DEBUG("Setting up OMX JPEG input port ...");
OMX_ERRORTYPE error;
OMX_PARAM_PORTDEFINITIONTYPE portdef;
if (omx_component_get_portdef(&omx->comp, &portdef, _INPUT_PORT) < 0) {
LOG_ERROR("... first");
return -1;
}
# define IFMT(_next) portdef.format.image._next
IFMT(nFrameWidth) = align_size(frame->width, 32);
IFMT(nFrameHeight) = frame->height;
IFMT(nStride) = align_size(frame->width, 32) << 1;
IFMT(nSliceHeight) = align_size(frame->height, 16);
IFMT(bFlagErrorConcealment) = OMX_FALSE;
IFMT(eCompressionFormat) = OMX_IMAGE_CodingUnused;
portdef.nBufferSize = ((frame->width * frame->height) << 1) * 2;
switch (frame->format) {
// https://www.fourcc.org/yuv.php
// Also see comments inside OMX_IVCommon.h
case V4L2_PIX_FMT_YUYV: IFMT(eColorFormat) = OMX_COLOR_FormatYCbYCr; break;
case V4L2_PIX_FMT_UYVY: IFMT(eColorFormat) = OMX_COLOR_FormatCbYCrY; break;
case V4L2_PIX_FMT_RGB565: IFMT(eColorFormat) = OMX_COLOR_Format16bitRGB565; break;
case V4L2_PIX_FMT_RGB24: IFMT(eColorFormat) = OMX_COLOR_Format24bitRGB888; break;
// TODO: найти устройство с RGB565 и протестить его.
// FIXME: RGB24 не работает нормально, нижняя половина экрана зеленая.
// FIXME: Китайский EasyCap тоже не работает, мусор на экране.
// Вероятно обе проблемы вызваны некорректной реализацией OMX на пае.
default: assert(0 && "Unsupported pixelformat");
}
# undef IFMT
if (omx_component_set_portdef(&omx->comp, &portdef) < 0) {
return -1;
}
if (omx_component_get_portdef(&omx->comp, &portdef, _INPUT_PORT) < 0) {
LOG_ERROR("... second");
return -1;
}
if (omx_component_enable_port(&omx->comp, _INPUT_PORT) < 0) {
return -1;
}
omx->i_input_port_enabled = true;
if ((error = OMX_AllocateBuffer(omx->comp, &omx->input_buf, _INPUT_PORT, NULL, portdef.nBufferSize)) != OMX_ErrorNone) {
LOG_ERROR_OMX(error, "Can't allocate OMX JPEG input buffer");
return -1;
}
return 0;
}
static int _omx_setup_output(omx_encoder_s *omx, unsigned quality) {
LOG_DEBUG("Setting up OMX JPEG output port ...");
OMX_ERRORTYPE error;
OMX_PARAM_PORTDEFINITIONTYPE portdef;
if (omx_component_get_portdef(&omx->comp, &portdef, _OUTPUT_PORT) < 0) {
LOG_ERROR("... first");
return -1;
}
# define OFMT(_next) portdef.format.image._next
OFMT(bFlagErrorConcealment) = OMX_FALSE;
OFMT(eCompressionFormat) = OMX_IMAGE_CodingJPEG;
OFMT(eColorFormat) = OMX_COLOR_FormatYCbYCr;
# undef OFMT
if (omx_component_set_portdef(&omx->comp, &portdef) < 0) {
return -1;
}
if (omx_component_get_portdef(&omx->comp, &portdef, _OUTPUT_PORT) < 0) {
LOG_ERROR("... second");
return -1;
}
# define SET_PARAM(_key, _value) { \
if ((error = OMX_SetParameter(omx->comp, OMX_IndexParam##_key, _value)) != OMX_ErrorNone) { \
LOG_ERROR_OMX(error, "Can't set OMX param %s", #_key); \
return -1; \
} \
}
OMX_CONFIG_BOOLEANTYPE exif;
OMX_INIT_STRUCTURE(exif);
exif.bEnabled = OMX_FALSE;
SET_PARAM(BrcmDisableEXIF, &exif);
OMX_PARAM_IJGSCALINGTYPE ijg;
OMX_INIT_STRUCTURE(ijg);
ijg.nPortIndex = _OUTPUT_PORT;
ijg.bEnabled = OMX_TRUE;
SET_PARAM(BrcmEnableIJGTableScaling, &ijg);
OMX_IMAGE_PARAM_QFACTORTYPE qfactor;
OMX_INIT_STRUCTURE(qfactor);
qfactor.nPortIndex = _OUTPUT_PORT;
qfactor.nQFactor = quality;
SET_PARAM(QFactor, &qfactor);
# undef SET_PARAM
if (omx_component_enable_port(&omx->comp, _OUTPUT_PORT) < 0) {
return -1;
}
omx->i_output_port_enabled = true;
if ((error = OMX_AllocateBuffer(omx->comp, &omx->output_buf, _OUTPUT_PORT, NULL, portdef.nBufferSize)) != OMX_ErrorNone) {
LOG_ERROR_OMX(error, "Can't allocate OMX JPEG output buffer");
return -1;
}
return 0;
}
static int _omx_encoder_clear_ports(omx_encoder_s *omx) {
OMX_ERRORTYPE error;
int retval = 0;
if (omx->i_output_port_enabled) {
retval -= omx_component_disable_port(&omx->comp, _OUTPUT_PORT);
omx->i_output_port_enabled = false;
}
if (omx->i_input_port_enabled) {
retval -= omx_component_disable_port(&omx->comp, _INPUT_PORT);
omx->i_input_port_enabled = false;
}
if (omx->input_buf) {
if ((error = OMX_FreeBuffer(omx->comp, _INPUT_PORT, omx->input_buf)) != OMX_ErrorNone) {
LOG_ERROR_OMX(error, "Can't free OMX JPEG input buffer");
// retval -= 1;
}
omx->input_buf = NULL;
}
if (omx->output_buf) {
if ((error = OMX_FreeBuffer(omx->comp, _OUTPUT_PORT, omx->output_buf)) != OMX_ErrorNone) {
LOG_ERROR_OMX(error, "Can't free OMX JPEG output buffer");
// retval -= 1;
}
omx->output_buf = NULL;
}
return retval;
}
static OMX_ERRORTYPE _omx_event_handler(
UNUSED OMX_HANDLETYPE comp,
OMX_PTR v_omx, OMX_EVENTTYPE event, OMX_U32 data1,
UNUSED OMX_U32 data2, UNUSED OMX_PTR event_data) {
// OMX calls this handler for all the events it emits
omx_encoder_s *omx = (omx_encoder_s *)v_omx;
if (event == OMX_EventError) {
LOG_ERROR_OMX((OMX_ERRORTYPE)data1, "OMX error event received");
omx->failed = true;
assert(vcos_semaphore_post(&omx->handler_sem) == VCOS_SUCCESS);
}
return OMX_ErrorNone;
}
static OMX_ERRORTYPE _omx_input_required_handler(
UNUSED OMX_HANDLETYPE comp,
OMX_PTR v_omx, UNUSED OMX_BUFFERHEADERTYPE *buf) {
// Called by OMX when the encoder component requires
// the input buffer to be filled with RAW image data
omx_encoder_s *omx = (omx_encoder_s *)v_omx;
omx->input_required = true;
assert(vcos_semaphore_post(&omx->handler_sem) == VCOS_SUCCESS);
return OMX_ErrorNone;
}
static OMX_ERRORTYPE _omx_output_available_handler(
UNUSED OMX_HANDLETYPE comp,
OMX_PTR v_omx, UNUSED OMX_BUFFERHEADERTYPE *buf) {
// Called by OMX when the encoder component has filled
// the output buffer with JPEG data
omx_encoder_s *omx = (omx_encoder_s *)v_omx;
omx->output_available = true;
assert(vcos_semaphore_post(&omx->handler_sem) == VCOS_SUCCESS);
return OMX_ErrorNone;
}

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -22,12 +22,26 @@
#pragma once
#include <stdlib.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 "../../device.h"
#include "../../../libs/tools.h"
#include "../../../libs/logging.h"
#include "../../../libs/frame.h"
#include "formatters.h"
#include "component.h"
#ifndef CFG_OMX_MAX_ENCODERS
# define CFG_OMX_MAX_ENCODERS 3 // Raspberry Pi limitation
@@ -35,24 +49,24 @@
#define OMX_MAX_ENCODERS ((unsigned)(CFG_OMX_MAX_ENCODERS))
struct omx_encoder_t {
OMX_HANDLETYPE encoder;
OMX_BUFFERHEADERTYPE *input_buffer;
OMX_BUFFERHEADERTYPE *output_buffer;
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_lock;
VCOS_SEMAPHORE_T handler_sem;
bool i_handler_lock;
bool i_handler_sem;
bool i_encoder;
bool i_input_port_enabled;
bool i_output_port_enabled;
};
} omx_encoder_s;
struct omx_encoder_t *omx_encoder_init(void);
void omx_encoder_destroy(struct omx_encoder_t *omx);
omx_encoder_s *omx_encoder_init(void);
void omx_encoder_destroy(omx_encoder_s *omx);
int omx_encoder_prepare(struct omx_encoder_t *omx, struct device_t *dev, unsigned quality);
int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev, unsigned index);
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);

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -22,24 +22,10 @@
#include "formatters.h"
#include <stdio.h>
#include <assert.h>
#include <IL/OMX_IVCommon.h>
#include <IL/OMX_Core.h>
#include "../../tools.h"
#define CASE_TO_STRING(_value) \
case _value: { return #_value; }
#define CASE_ASSERT(_msg, _value) default: { \
char *_assert_buf; A_CALLOC(_assert_buf, 128); \
sprintf(_assert_buf, _msg ": 0x%08x", _value); \
assert(0 && _assert_buf); \
}
const char *omx_error_to_string(OMX_ERRORTYPE error) {
switch (error) {
CASE_TO_STRING(OMX_ErrorNone);
@@ -101,9 +87,9 @@ const char *omx_state_to_string(OMX_STATETYPE state) {
CASE_TO_STRING(OMX_StateWaitForResources);
// cppcheck-suppress constArgument
// cppcheck-suppress knownArgument
CASE_ASSERT("Unsupported OMX state", state);
default: break;
}
assert(0 && "Unsupported OMX state");
}
#undef CASE_ASSERT
#undef CASE_TO_STRING

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 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,15 @@
#pragma once
#include <stdio.h>
#include <assert.h>
#include <IL/OMX_IVCommon.h>
#include <IL/OMX_Core.h>
#include <IL/OMX_Image.h>
#include "../../logging.h"
#include "../../../libs/tools.h"
#include "../../../libs/logging.h"
#define LOG_ERROR_OMX(_error, _msg, ...) { \

130
src/ustreamer/gpio/gpio.c Normal file
View File

@@ -0,0 +1,130 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 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 "gpio.h"
gpio_s gpio = {
.path = "/dev/gpiochip0",
.consumer_prefix = "ustreamer",
# define MAKE_OUTPUT(_role) { \
.pin = -1, \
.role = _role, \
.consumer = NULL, \
.line = NULL, \
.state = false \
}
.prog_running = MAKE_OUTPUT("prog-running"),
.stream_online = MAKE_OUTPUT("stream-online"),
.has_http_clients = MAKE_OUTPUT("has-http-clients"),
# undef MAKE_OUTPUT
// mutex uninitialized
.chip = NULL
};
static void _gpio_output_init(gpio_output_s *output);
static void _gpio_output_destroy(gpio_output_s *output);
void gpio_init(void) {
assert(gpio.chip == NULL);
if (
gpio.prog_running.pin >= 0
|| gpio.stream_online.pin >= 0
|| gpio.has_http_clients.pin >= 0
) {
A_MUTEX_INIT(&gpio.mutex);
LOG_INFO("GPIO: Using chip device: %s", gpio.path);
if ((gpio.chip = gpiod_chip_open(gpio.path)) != NULL) {
_gpio_output_init(&gpio.prog_running);
_gpio_output_init(&gpio.stream_online);
_gpio_output_init(&gpio.has_http_clients);
} else {
LOG_PERROR("GPIO: Can't initialize chip device %s", gpio.path);
}
}
}
void gpio_destroy(void) {
_gpio_output_destroy(&gpio.prog_running);
_gpio_output_destroy(&gpio.stream_online);
_gpio_output_destroy(&gpio.has_http_clients);
if (gpio.chip) {
gpiod_chip_close(gpio.chip);
gpio.chip = NULL;
A_MUTEX_DESTROY(&gpio.mutex);
}
}
int gpio_inner_set(gpio_output_s *output, bool state) {
int retval = 0;
assert(gpio.chip);
assert(output->line);
assert(output->state != state); // Must be checked in macro for the performance
A_MUTEX_LOCK(&gpio.mutex);
if (gpiod_line_set_value(output->line, (int)state) < 0) { \
LOG_PERROR("GPIO: Can't write value %d to line %s (will be disabled)", state, output->consumer); \
_gpio_output_destroy(output);
retval = -1;
}
A_MUTEX_UNLOCK(&gpio.mutex);
return retval;
}
static void _gpio_output_init(gpio_output_s *output) {
assert(gpio.chip);
assert(output->line == NULL);
A_CALLOC(output->consumer, strlen(gpio.consumer_prefix) + strlen(output->role) + 16);
sprintf(output->consumer, "%s::%s", gpio.consumer_prefix, output->role);
if (output->pin >= 0) {
if ((output->line = gpiod_chip_get_line(gpio.chip, output->pin)) != NULL) {
if (gpiod_line_request_output(output->line, output->consumer, 0) < 0) {
LOG_PERROR("GPIO: Can't request pin=%d as %s", output->pin, output->consumer);
_gpio_output_destroy(output);
}
} else {
LOG_PERROR("GPIO: Can't get pin=%d as %s", output->pin, output->consumer);
}
}
}
static void _gpio_output_destroy(gpio_output_s *output) {
if (output->line) {
gpiod_line_release(output->line);
output->line = NULL;
}
if (output->consumer) {
free(output->consumer);
output->consumer = NULL;
}
output->state = false;
}

87
src/ustreamer/gpio/gpio.h Normal file
View File

@@ -0,0 +1,87 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 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 <string.h>
#include <assert.h>
#include <pthread.h>
#include <gpiod.h>
#include "../../libs/tools.h"
#include "../../libs/logging.h"
#include "../../libs/threading.h"
typedef struct {
int pin;
const char *role;
char *consumer;
struct gpiod_line *line;
bool state;
} gpio_output_s;
typedef struct {
char *path;
char *consumer_prefix;
gpio_output_s prog_running;
gpio_output_s stream_online;
gpio_output_s has_http_clients;
pthread_mutex_t mutex;
struct gpiod_chip *chip;
} gpio_s;
extern gpio_s gpio;
void gpio_init(void);
void gpio_destroy(void);
int gpio_inner_set(gpio_output_s *output, bool state);
#define SET_STATE(_output, _state) { \
if (_output.line && _output.state != _state) { \
if (!gpio_inner_set(&_output, _state)) { \
_output.state = _state; \
} \
} \
}
INLINE void gpio_set_prog_running(bool state) {
SET_STATE(gpio.prog_running, state);
}
INLINE void gpio_set_stream_online(bool state) {
SET_STATE(gpio.stream_online, state);
}
INLINE void gpio_set_has_http_clients(bool state) {
SET_STATE(gpio.has_http_clients, state);
}
#undef SET_STATE

View File

@@ -0,0 +1,391 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "encoder.h"
static void _h264_encoder_cleanup(h264_encoder_s *enc);
static int _h264_encoder_compress_raw(h264_encoder_s *enc, const frame_s *src, int src_vcsm_handle, frame_s *dest, bool force_key);
static void _mmal_callback(MMAL_WRAPPER_T *wrapper);
static const char *_mmal_error_to_string(MMAL_STATUS_T error);
#define LOG_ERROR_MMAL(_error, _msg, ...) { \
LOG_ERROR(_msg ": %s", ##__VA_ARGS__, _mmal_error_to_string(_error)); \
}
h264_encoder_s *h264_encoder_init(unsigned bitrate, unsigned gop, unsigned fps) {
LOG_INFO("H264: Initializing MMAL encoder ...");
LOG_INFO("H264: Using bitrate: %u Kbps", bitrate);
LOG_INFO("H264: Using GOP: %u", gop);
h264_encoder_s *enc;
A_CALLOC(enc, 1);
enc->bitrate = bitrate; // Kbps
enc->gop = gop; // Interval between keyframes
enc->fps = fps;
enc->last_online = -1;
if (vcos_semaphore_create(&enc->handler_sem, "h264_handler_sem", 0) != VCOS_SUCCESS) {
LOG_PERROR("H264: Can't create VCOS semaphore");
goto error;
}
enc->i_handler_sem = true;
MMAL_STATUS_T error = mmal_wrapper_create(&enc->wrapper, MMAL_COMPONENT_DEFAULT_VIDEO_ENCODER);
if (error != MMAL_SUCCESS) {
LOG_ERROR_MMAL(error, "H264: Can't create MMAL wrapper");
enc->wrapper = NULL;
goto error;
}
enc->wrapper->user_data = (void *)enc;
enc->wrapper->callback = _mmal_callback;
return enc;
error:
h264_encoder_destroy(enc);
return NULL;
}
void h264_encoder_destroy(h264_encoder_s *enc) {
LOG_INFO("H264: Destroying MMAL encoder ...");
_h264_encoder_cleanup(enc);
if (enc->wrapper) {
MMAL_STATUS_T error = mmal_wrapper_destroy(enc->wrapper);
if (error != MMAL_SUCCESS) {
LOG_ERROR_MMAL(error, "H264: Can't destroy MMAL encoder");
}
}
if (enc->i_handler_sem) {
vcos_semaphore_delete(&enc->handler_sem);
}
free(enc);
}
bool h264_encoder_is_prepared_for(h264_encoder_s *enc, const frame_s *frame, bool zero_copy) {
# define EQ(_field) (enc->_field == frame->_field)
return (EQ(width) && EQ(height) && EQ(format) && EQ(stride) && (enc->zero_copy == zero_copy));
# undef EQ
}
int h264_encoder_prepare(h264_encoder_s *enc, const frame_s *frame, bool zero_copy) {
LOG_INFO("H264: Configuring MMAL encoder: zero_copy=%d ...", zero_copy);
_h264_encoder_cleanup(enc);
enc->width = frame->width;
enc->height = frame->height;
enc->format = frame->format;
enc->stride = frame->stride;
enc->zero_copy = zero_copy;
if (align_size(frame->width, 32) != frame->width && frame_get_padding(frame) == 0) {
LOG_ERROR("H264: MMAL encoder can't handle unaligned width");
goto error;
}
MMAL_STATUS_T error;
# define PREPARE_PORT(_id) { \
enc->_id##_port = enc->wrapper->_id[0]; \
if (enc->_id##_port->is_enabled) { \
if ((error = mmal_wrapper_port_disable(enc->_id##_port)) != MMAL_SUCCESS) { \
LOG_ERROR_MMAL(error, "H264: Can't disable MMAL %s port while configuring", #_id); \
goto error; \
} \
} \
}
# define COMMIT_PORT(_id) { \
if ((error = mmal_port_format_commit(enc->_id##_port)) != MMAL_SUCCESS) { \
LOG_ERROR_MMAL(error, "H264: Can't commit MMAL %s port", #_id); \
goto error; \
} \
}
# define SET_PORT_PARAM(_id, _type, _key, _value) { \
if ((error = mmal_port_parameter_set_##_type(enc->_id##_port, MMAL_PARAMETER_##_key, _value)) != MMAL_SUCCESS) { \
LOG_ERROR_MMAL(error, "H264: Can't set %s for the %s port", #_key, #_id); \
goto error; \
} \
}
# define ENABLE_PORT(_id) { \
if ((error = mmal_wrapper_port_enable(enc->_id##_port, MMAL_WRAPPER_FLAG_PAYLOAD_ALLOCATE)) != MMAL_SUCCESS) { \
LOG_ERROR_MMAL(error, "H264: Can't enable MMAL %s port", #_id); \
goto error; \
} \
}
{
PREPARE_PORT(input);
# define IFMT(_next) enc->input_port->format->_next
IFMT(type) = MMAL_ES_TYPE_VIDEO;
switch (frame->format) {
case V4L2_PIX_FMT_YUYV: IFMT(encoding) = MMAL_ENCODING_YUYV; break;
case V4L2_PIX_FMT_UYVY: IFMT(encoding) = MMAL_ENCODING_UYVY; break;
case V4L2_PIX_FMT_RGB565: IFMT(encoding) = MMAL_ENCODING_RGB16; break;
case V4L2_PIX_FMT_RGB24: IFMT(encoding) = MMAL_ENCODING_RGB24; break;
default: assert(0 && "Unsupported pixelformat");
}
IFMT(es->video.width) = align_size(frame->width, 32);
IFMT(es->video.height) = align_size(frame->height, 16);
IFMT(es->video.crop.x) = 0;
IFMT(es->video.crop.y) = 0;
IFMT(es->video.crop.width) = frame->width;
IFMT(es->video.crop.height) = frame->height;
IFMT(flags) = MMAL_ES_FORMAT_FLAG_FRAMED;
enc->input_port->buffer_size = 1000 * 1000;
enc->input_port->buffer_num = enc->input_port->buffer_num_recommended * 4;
# undef IFMT
COMMIT_PORT(input);
SET_PORT_PARAM(input, boolean, ZERO_COPY, zero_copy);
}
{
PREPARE_PORT(output);
# define OFMT(_next) enc->output_port->format->_next
OFMT(type) = MMAL_ES_TYPE_VIDEO;
OFMT(encoding) = MMAL_ENCODING_H264;
OFMT(encoding_variant) = MMAL_ENCODING_VARIANT_H264_DEFAULT;
OFMT(bitrate) = enc->bitrate * 1000;
OFMT(es->video.frame_rate.num) = enc->fps;
OFMT(es->video.frame_rate.den) = 1;
enc->output_port->buffer_size = enc->output_port->buffer_size_recommended * 4;
enc->output_port->buffer_num = enc->output_port->buffer_num_recommended;
# undef OFMT
COMMIT_PORT(output);
{
MMAL_PARAMETER_VIDEO_PROFILE_T profile;
MEMSET_ZERO(profile);
profile.hdr.id = MMAL_PARAMETER_PROFILE;
profile.hdr.size = sizeof(profile);
// http://blog.mediacoderhq.com/h264-profiles-and-levels
profile.profile[0].profile = MMAL_VIDEO_PROFILE_H264_CONSTRAINED_BASELINE;
profile.profile[0].level = MMAL_VIDEO_LEVEL_H264_4; // Supports 1080p
if ((error = mmal_port_parameter_set(enc->output_port, &profile.hdr)) != MMAL_SUCCESS) {
LOG_ERROR_MMAL(error, "H264: Can't set MMAL_PARAMETER_PROFILE for the output port");
goto error;
}
}
SET_PORT_PARAM(output, boolean, ZERO_COPY, MMAL_TRUE);
SET_PORT_PARAM(output, uint32, INTRAPERIOD, enc->gop);
SET_PORT_PARAM(output, uint32, NALUNITFORMAT, MMAL_VIDEO_NALUNITFORMAT_STARTCODES);
SET_PORT_PARAM(output, boolean, MINIMISE_FRAGMENTATION, MMAL_TRUE);
SET_PORT_PARAM(output, uint32, MB_ROWS_PER_SLICE, 0);
SET_PORT_PARAM(output, boolean, VIDEO_IMMUTABLE_INPUT, MMAL_TRUE);
SET_PORT_PARAM(output, boolean, VIDEO_DROPPABLE_PFRAMES, MMAL_FALSE);
SET_PORT_PARAM(output, boolean, VIDEO_ENCODE_INLINE_HEADER, MMAL_TRUE); // SPS/PPS: https://github.com/raspberrypi/userland/issues/443
SET_PORT_PARAM(output, uint32, VIDEO_BIT_RATE, enc->bitrate * 1000);
SET_PORT_PARAM(output, uint32, VIDEO_ENCODE_PEAK_RATE, enc->bitrate * 1000);
SET_PORT_PARAM(output, uint32, VIDEO_ENCODE_MIN_QUANT, 16);
SET_PORT_PARAM(output, uint32, VIDEO_ENCODE_MAX_QUANT, 34);
SET_PORT_PARAM(output, uint32, VIDEO_ENCODE_FRAME_LIMIT_BITS, 1000000);
SET_PORT_PARAM(output, uint32, VIDEO_ENCODE_H264_AU_DELIMITERS, MMAL_FALSE);
}
ENABLE_PORT(input);
ENABLE_PORT(output);
enc->ready = true;
return 0;
error:
_h264_encoder_cleanup(enc);
LOG_ERROR("H264: Encoder disabled due error (prepare)");
return -1;
# undef ENABLE_PORT
# undef SET_PORT_PARAM
# undef COMMIT_PORT
# undef PREPARE_PORT
}
static void _h264_encoder_cleanup(h264_encoder_s *enc) {
MMAL_STATUS_T error;
# define DISABLE_PORT(_id) { \
if (enc->_id##_port) { \
if ((error = mmal_wrapper_port_disable(enc->_id##_port)) != MMAL_SUCCESS) { \
LOG_ERROR_MMAL(error, "H264: Can't disable MMAL %s port", #_id); \
} \
enc->_id##_port = NULL; \
} \
}
DISABLE_PORT(input);
DISABLE_PORT(output);
# undef DISABLE_PORT
if (enc->wrapper) {
enc->wrapper->status = MMAL_SUCCESS; // Это реально надо?
}
enc->last_online = -1;
enc->ready = false;
}
int h264_encoder_compress(h264_encoder_s *enc, const frame_s *src, int src_vcsm_handle, frame_s *dest, bool force_key) {
assert(enc->ready);
assert(src->used > 0);
assert(enc->width == src->width);
assert(enc->height == src->height);
assert(enc->format == src->format);
assert(enc->stride == src->stride);
frame_copy_meta(src, dest);
dest->encode_begin_ts = get_now_monotonic();
dest->format = V4L2_PIX_FMT_H264;
dest->stride = 0;
force_key = (force_key || enc->last_online != src->online);
if (_h264_encoder_compress_raw(enc, src, src_vcsm_handle, dest, force_key) < 0) {
_h264_encoder_cleanup(enc);
LOG_ERROR("H264: Encoder disabled due error (compress)");
return -1;
}
dest->encode_end_ts = get_now_monotonic();
LOG_VERBOSE("H264: Compressed new frame: size=%zu, time=%0.3Lf, force_key=%d",
dest->used, dest->encode_end_ts - dest->encode_begin_ts, force_key);
enc->last_online = src->online;
return 0;
}
static int _h264_encoder_compress_raw(h264_encoder_s *enc, const frame_s *src, int src_vcsm_handle, frame_s *dest, bool force_key) {
LOG_DEBUG("H264: Compressing new frame; force_key=%d ...", force_key);
MMAL_STATUS_T error;
if (force_key) {
if ((error = mmal_port_parameter_set_boolean(
enc->output_port,
MMAL_PARAMETER_VIDEO_REQUEST_I_FRAME,
MMAL_TRUE
)) != MMAL_SUCCESS) {
LOG_ERROR_MMAL(error, "H264: Can't request keyframe");
return -1;
}
}
MMAL_BUFFER_HEADER_T *out = NULL;
MMAL_BUFFER_HEADER_T *in = NULL;
bool eos = false;
bool sent = false;
dest->used = 0;
while (!eos) {
out = NULL;
while (mmal_wrapper_buffer_get_empty(enc->output_port, &out, 0) == MMAL_SUCCESS) {
if ((error = mmal_port_send_buffer(enc->output_port, out)) != MMAL_SUCCESS) {
LOG_ERROR_MMAL(error, "H264: Can't send MMAL output buffer");
return -1;
}
}
in = NULL;
if (!sent && mmal_wrapper_buffer_get_empty(enc->input_port, &in, 0) == MMAL_SUCCESS) {
if (enc->zero_copy && src_vcsm_handle > 0) {
in->data = (uint8_t *)vcsm_vc_hdl_from_hdl(src_vcsm_handle);
} else {
in->data = src->data;
}
in->alloc_size = src->used;
in->length = src->used;
in->offset = 0;
in->flags = MMAL_BUFFER_HEADER_FLAG_EOS;
if ((error = mmal_port_send_buffer(enc->input_port, in)) != MMAL_SUCCESS) {
LOG_ERROR_MMAL(error, "H264: Can't send MMAL input buffer");
return -1;
}
sent = true;
}
error = mmal_wrapper_buffer_get_full(enc->output_port, &out, 0);
if (error == MMAL_EAGAIN) {
vcos_semaphore_wait(&enc->handler_sem);
continue;
} else if (error != MMAL_SUCCESS) {
LOG_ERROR_MMAL(error, "H264: Can't get MMAL output buffer");
return -1;
}
frame_append_data(dest, out->data, out->length);
eos = out->flags & MMAL_BUFFER_HEADER_FLAG_EOS;
mmal_buffer_header_release(out);
}
if ((error = mmal_port_flush(enc->output_port)) != MMAL_SUCCESS) {
LOG_ERROR_MMAL(error, "H264: Can't flush MMAL output buffer; ignored");
}
return 0;
}
static void _mmal_callback(MMAL_WRAPPER_T *wrapper) {
vcos_semaphore_post(&((h264_encoder_s *)(wrapper->user_data))->handler_sem);
}
static const char *_mmal_error_to_string(MMAL_STATUS_T error) {
// http://www.jvcref.com/files/PI/documentation/html/group___mmal_types.html
# define CASE_ERROR(_name, _msg) case MMAL_##_name: return "MMAL_" #_name " [" _msg "]"
switch (error) {
case MMAL_SUCCESS: return "MMAL_SUCCESS";
CASE_ERROR(ENOMEM, "Out of memory");
CASE_ERROR(ENOSPC, "Out of resources");
CASE_ERROR(EINVAL, "Invalid argument");
CASE_ERROR(ENOSYS, "Function not implemented");
CASE_ERROR(ENOENT, "No such file or directory");
CASE_ERROR(ENXIO, "No such device or address");
CASE_ERROR(EIO, "IO error");
CASE_ERROR(ESPIPE, "Illegal seek");
CASE_ERROR(ECORRUPT, "Data is corrupt");
CASE_ERROR(ENOTREADY, "Component is not ready");
CASE_ERROR(ECONFIG, "Component is not configured");
CASE_ERROR(EISCONN, "Port is already connected");
CASE_ERROR(ENOTCONN, "Port is disconnected");
CASE_ERROR(EAGAIN, "Resource temporarily unavailable");
CASE_ERROR(EFAULT, "Bad address");
case MMAL_STATUS_MAX: break; // Makes cpplint happy
}
return "Unknown error";
# undef CASE_ERROR
}
#undef LOG_ERROR_MMAL

View File

@@ -0,0 +1,70 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 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 <assert.h>
#include <linux/videodev2.h>
#include <interface/mmal/mmal.h>
#include <interface/mmal/mmal_format.h>
#include <interface/mmal/util/mmal_default_components.h>
#include <interface/mmal/util/mmal_component_wrapper.h>
#include <interface/mmal/util/mmal_util_params.h>
#include <interface/vcsm/user-vcsm.h>
#include "../../libs/tools.h"
#include "../../libs/logging.h"
#include "../../libs/frame.h"
typedef struct {
unsigned bitrate; // Kbit-per-sec
unsigned gop; // Interval between keyframes
unsigned fps;
MMAL_WRAPPER_T *wrapper;
MMAL_PORT_T *input_port;
MMAL_PORT_T *output_port;
VCOS_SEMAPHORE_T handler_sem;
bool i_handler_sem;
int last_online;
unsigned width;
unsigned height;
unsigned format;
unsigned stride;
bool zero_copy;
bool ready;
} h264_encoder_s;
h264_encoder_s *h264_encoder_init(unsigned bitrate, unsigned gop, unsigned fps);
void h264_encoder_destroy(h264_encoder_s *enc);
bool h264_encoder_is_prepared_for(h264_encoder_s *enc, const frame_s *frame, bool zero_copy);
int h264_encoder_prepare(h264_encoder_s *enc, const frame_s *frame, bool zero_copy);
int h264_encoder_compress(h264_encoder_s *enc, const frame_s *src, int src_vcsm_handle, frame_s *dest, bool force_key);

View File

@@ -0,0 +1,94 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 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 "stream.h"
h264_stream_s *h264_stream_init(memsink_s *sink, unsigned bitrate, unsigned gop) {
h264_stream_s *h264;
A_CALLOC(h264, 1);
h264->sink = sink;
h264->tmp_src = frame_init("h264_tmp_src");
h264->dest = frame_init("h264_dest");
atomic_init(&h264->online, false);
// FIXME: 30 or 0? https://github.com/6by9/yavta/blob/master/yavta.c#L210
if ((h264->enc = h264_encoder_init(bitrate, gop, 0)) == NULL) {
goto error;
}
return h264;
error:
h264_stream_destroy(h264);
return NULL;
}
void h264_stream_destroy(h264_stream_s *h264) {
if (h264->enc) {
h264_encoder_destroy(h264->enc);
}
frame_destroy(h264->dest);
frame_destroy(h264->tmp_src);
free(h264);
}
void h264_stream_process(h264_stream_s *h264, const frame_s *frame, int vcsm_handle, bool force_key) {
if (!memsink_server_check(h264->sink, frame)) {
return;
}
long double now = get_now_monotonic();
bool zero_copy = false;
if (is_jpeg(frame->format)) {
assert(vcsm_handle <= 0);
LOG_DEBUG("H264: Input frame is JPEG; decoding ...");
if (unjpeg(frame, h264->tmp_src, true) < 0) {
return;
}
frame = h264->tmp_src;
LOG_VERBOSE("H264: JPEG decoded; time=%.3Lf", get_now_monotonic() - now);
} else if (vcsm_handle > 0) {
LOG_DEBUG("H264: Zero-copy available for the input");
zero_copy = true;
} else {
LOG_DEBUG("H264: Copying source to tmp buffer ...");
frame_copy(frame, h264->tmp_src);
frame = h264->tmp_src;
LOG_VERBOSE("H264: Source copied; time=%.3Lf", get_now_monotonic() - now);
}
bool online = false;
if (!h264_encoder_is_prepared_for(h264->enc, frame, zero_copy)) {
h264_encoder_prepare(h264->enc, frame, zero_copy);
}
if (h264->enc->ready) {
if (h264_encoder_compress(h264->enc, frame, vcsm_handle, h264->dest, force_key) == 0) {
online = !memsink_server_put(h264->sink, h264->dest);
}
}
atomic_store(&h264->online, online);
}

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 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,35 +24,26 @@
#include <stdbool.h>
#include <stdatomic.h>
#include <assert.h>
#include <pthread.h>
#include "../../libs/tools.h"
#include "../../libs/logging.h"
#include "../../libs/frame.h"
#include "../../libs/memsink.h"
#include "../../libs/unjpeg.h"
#include "picture.h"
#include "device.h"
#include "encoder.h"
struct process_t {
atomic_bool stop;
atomic_bool slowdown;
};
struct stream_t {
struct picture_t *picture;
bool online;
unsigned captured_fps;
atomic_bool updated;
pthread_mutex_t mutex;
struct process_t *proc;
struct device_t *dev;
struct encoder_t *encoder;
};
typedef struct {
memsink_s *sink;
frame_s *tmp_src;
frame_s *dest;
h264_encoder_s *enc;
atomic_bool online;
} h264_stream_s;
struct stream_t *stream_init(struct device_t *dev, struct encoder_t *encoder);
void stream_destroy(struct stream_t *stream);
void stream_loop(struct stream_t *stream);
void stream_loop_break(struct stream_t *stream);
void stream_switch_slowdown(struct stream_t *stream, bool slowdown);
h264_stream_s *h264_stream_init(memsink_s *sink, unsigned bitrate, unsigned gop);
void h264_stream_destroy(h264_stream_s *h264);
void h264_stream_process(h264_stream_s *h264, const frame_s *frame, int vcsm_handle, bool force_key);

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -20,32 +20,39 @@
*****************************************************************************/
#pragma once
#include <stddef.h>
#include <stdbool.h>
#include "bev.h"
struct picture_t {
unsigned char *data;
size_t used;
size_t allocated;
unsigned width;
unsigned height;
long double grab_ts;
long double encode_begin_ts;
long double encode_end_ts;
};
char *bufferevent_my_format_reason(short what) {
char *reason;
A_CALLOC(reason, 2048);
char perror_buf[1024] = {0};
char *perror_ptr = errno_to_string(EVUTIL_SOCKET_ERROR(), perror_buf, 1024); // evutil_socket_error_to_string() is not thread-safe
bool first = true;
struct picture_t *picture_init(void);
void picture_destroy(struct picture_t *picture);
strcat(reason, perror_ptr);
strcat(reason, " (");
size_t picture_get_generous_size(unsigned width, unsigned height);
# define FILL_REASON(_bev, _name) { \
if (what & _bev) { \
if (first) { \
first = false; \
} else { \
strcat(reason, ","); \
} \
strcat(reason, _name); \
} \
}
void picture_realloc_data(struct picture_t *picture, size_t size);
void picture_set_data(struct picture_t *picture, const unsigned char *data, size_t size);
void picture_append_data(struct picture_t *picture, const unsigned char *data, size_t size);
FILL_REASON(BEV_EVENT_READING, "reading");
FILL_REASON(BEV_EVENT_WRITING, "writing");
FILL_REASON(BEV_EVENT_ERROR, "error");
FILL_REASON(BEV_EVENT_TIMEOUT, "timeout");
FILL_REASON(BEV_EVENT_EOF, "eof"); // cppcheck-suppress unreadVariable
void picture_copy(const struct picture_t *src, struct picture_t *dest);
bool picture_compare(const struct picture_t *a, const struct picture_t *b);
# undef FILL_REASON
strcat(reason, ")");
return reason;
}

35
src/ustreamer/http/bev.h Normal file
View File

@@ -0,0 +1,35 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 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 <errno.h>
#include <event2/util.h>
#include <event2/bufferevent.h>
#include "../../libs/tools.h"
#include "../../libs/logging.h"
char *bufferevent_my_format_reason(short what);

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -22,12 +22,6 @@
#include "mime.h"
#include <string.h>
#include <event2/util.h>
#include "../tools.h"
static const struct {
const char *ext;
@@ -53,15 +47,13 @@ static const struct {
const char *guess_mime_type(const char *path) {
char *dot;
char *ext;
dot = strrchr(path, '.');
// FIXME: false-positive cppcheck
char *dot = strrchr(path, '.'); // cppcheck-suppress ctunullpointer
if (dot == NULL || strchr(dot, '/') != NULL) {
goto misc;
}
ext = dot + 1;
char *ext = dot + 1;
for (unsigned index = 0; index < ARRAY_LEN(_MIME_TYPES); ++index) {
if (!evutil_ascii_strcasecmp(ext, _MIME_TYPES[index].ext)) {
return _MIME_TYPES[index].mime;

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 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,5 +22,11 @@
#pragma once
#include <string.h>
#include <event2/util.h>
#include "../../libs/tools.h"
const char *guess_mime_type(const char *str);

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -22,14 +22,6 @@
#include "path.h"
#ifdef TEST_HTTP_PATH
# include <stdio.h>
# include <stdlib.h>
#endif
#include <string.h>
#include "../tools.h"
char *simplify_request_path(const char *str) {
// Based on Lighttpd sources:

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 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,5 +22,13 @@
#pragma once
#ifdef TEST_HTTP_PATH
# include <stdio.h>
# include <stdlib.h>
#endif
#include <string.h>
#include "../../libs/tools.h"
char *simplify_request_path(const char *str);

File diff suppressed because it is too large Load Diff

153
src/ustreamer/http/server.h Normal file
View File

@@ -0,0 +1,153 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdatomic.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <netinet/tcp.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <event2/util.h>
#include <event2/event.h>
#include <event2/thread.h>
#include <event2/http.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <event2/keyvalq_struct.h>
#include <uuid/uuid.h>
#ifndef EVTHREAD_USE_PTHREADS_IMPLEMENTED
# error Required libevent-pthreads support
#endif
#include "../../libs/tools.h"
#include "../../libs/threading.h"
#include "../../libs/logging.h"
#include "../../libs/process.h"
#include "../../libs/frame.h"
#include "../../libs/base64.h"
#include "../data/index_html.h"
#include "../encoder.h"
#include "../stream.h"
#ifdef WITH_GPIO
# include "../gpio/gpio.h"
#endif
#include "bev.h"
#include "unix.h"
#include "uri.h"
#include "mime.h"
#include "static.h"
typedef struct stream_client_sx {
struct server_sx *server;
struct evhttp_request *request;
char *key;
bool extra_headers;
bool advance_headers;
bool dual_final_frames;
bool zero_data;
char id[37]; // ex. "1b4e28ba-2fa1-11d2-883f-0016d3cca427" + "\0"
bool need_initial;
bool need_first_frame;
bool updated_prev;
unsigned fps;
unsigned fps_accum;
long long fps_accum_second;
struct stream_client_sx *prev;
struct stream_client_sx *next;
} stream_client_s;
typedef struct {
frame_s *frame;
unsigned captured_fps;
unsigned queued_fps;
unsigned dropped;
long double expose_begin_ts;
long double expose_cmp_ts;
long double expose_end_ts;
bool notify_last_online;
unsigned notify_last_width;
unsigned notify_last_height;
} exposed_s;
typedef struct {
struct event_base *base;
struct evhttp *http;
evutil_socket_t unix_fd;
char *auth_token;
struct event *refresh;
stream_s *stream;
exposed_s *exposed;
stream_client_s *stream_clients;
unsigned stream_clients_count;
} server_runtime_s;
typedef struct server_sx {
char *host;
unsigned port;
char *unix_path;
bool unix_rm;
mode_t unix_mode;
bool tcp_nodelay;
unsigned timeout;
char *user;
char *passwd;
char *static_path;
char *allow_origin;
unsigned drop_same_frames;
unsigned fake_width;
unsigned fake_height;
bool notify_parent;
server_runtime_s *run;
} server_s;
server_s *server_init(stream_s *stream);
void server_destroy(server_s *server);
int server_listen(server_s *server);
void server_loop(server_s *server);
void server_loop_break(server_s *server);

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 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,33 +22,20 @@
#include "static.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include "../tools.h"
#include "../logging.h"
#include "path.h"
char *find_static_file_path(const char *root_path, const char *request_path) {
char *simplified_path;
char *path = NULL;
struct stat st;
simplified_path = simplify_request_path(request_path);
char *simplified_path = simplify_request_path(request_path);
if (simplified_path[0] == '\0') {
LOG_VERBOSE("HTTP: Invalid request path %s to static", request_path);
goto error;
}
A_CALLOC(path, strlen(root_path) + strlen(simplified_path) + 32);
A_CALLOC(path, strlen(root_path) + strlen(simplified_path) + 16);
sprintf(path, "%s/%s", root_path, simplified_path);
struct stat st;
# define LOAD_STAT { \
if (lstat(path, &st) < 0) { \
LOG_VERBOSE_PERROR("HTTP: Can't stat() static path %s", path); \

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 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,5 +22,17 @@
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include "../../libs/tools.h"
#include "../../libs/logging.h"
#include "path.h"
char *find_static_file_path(const char *root_path, const char *request_path);

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 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,25 +22,8 @@
#include "unix.h"
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <assert.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <event2/http.h>
#include <event2/util.h>
#include "../tools.h"
#include "../logging.h"
evutil_socket_t evhttp_my_bind_unix(struct evhttp *http, const char *path, bool rm, mode_t mode) {
evutil_socket_t fd = -1;
struct sockaddr_un addr;
# define MAX_SUN_PATH (sizeof(addr.sun_path) - 1)
@@ -56,6 +39,7 @@ evutil_socket_t evhttp_my_bind_unix(struct evhttp *http, const char *path, bool
# undef MAX_SUN_PATH
evutil_socket_t fd = -1;
assert((fd = socket(AF_UNIX, SOCK_STREAM, 0)) >= 0);
assert(!evutil_make_socket_nonblocking(fd));

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 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,11 +23,20 @@
#pragma once
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <assert.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <event2/http.h>
#include <event2/util.h>
#include "../../libs/tools.h"
#include "../../libs/logging.h"
evutil_socket_t evhttp_my_bind_unix(struct evhttp *http, const char *path, bool rm, mode_t mode);

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -22,17 +22,10 @@
#include "uri.h"
#include <stdbool.h>
#include <event2/http.h>
#include <event2/util.h>
#include <event2/keyvalq_struct.h>
bool uri_get_true(struct evkeyvalq *params, const char *key) {
const char *value_str;
if ((value_str = evhttp_find_header(params, key)) != NULL) {
const char *value_str = evhttp_find_header(params, key);
if (value_str != NULL) {
if (
value_str[0] == '1'
|| !evutil_ascii_strcasecmp(value_str, "true")
@@ -45,9 +38,8 @@ bool uri_get_true(struct evkeyvalq *params, const char *key) {
}
char *uri_get_string(struct evkeyvalq *params, const char *key) {
const char *value_str;
if ((value_str = evhttp_find_header(params, key)) != NULL) {
const char *value_str = evhttp_find_header(params, key);
if (value_str != NULL) {
return evhttp_encode_uri(value_str);
}
return NULL;

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 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 <stdbool.h>
#include <event2/util.h>
#include <event2/http.h>
#include <event2/keyvalq_struct.h>

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 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,29 +31,35 @@
#endif
#include <stdio.h>
#include <stdbool.h>
#include <signal.h>
#include <pthread.h>
#ifdef WITH_OMX
# include <bcm_host.h>
# include <IL/OMX_Core.h>
#endif
#include "../libs/tools.h"
#include "../libs/threading.h"
#include "../libs/logging.h"
#include "tools.h"
#include "threading.h"
#include "logging.h"
#include "options.h"
#include "device.h"
#include "encoder.h"
#include "stream.h"
#include "http/server.h"
#ifdef WITH_GPIO
# include "gpio.h"
# include "gpio/gpio.h"
#endif
struct _main_context_t {
struct stream_t *stream;
struct http_server_t *server;
};
typedef struct {
stream_s *stream;
server_s *server;
} _main_context_s;
static struct _main_context_t *_ctx;
static _main_context_s *_ctx;
static void _block_thread_signals(void) {
sigset_t mask;
@@ -73,93 +79,116 @@ static void *_stream_loop_thread(UNUSED void *arg) {
static void *_server_loop_thread(UNUSED void *arg) {
A_THREAD_RENAME("http");
_block_thread_signals();
http_server_loop(_ctx->server);
server_loop(_ctx->server);
return NULL;
}
static void _signal_handler(int signum) {
LOG_INFO_NOLOCK("===== Stopping by %s =====", (signum == SIGTERM ? "SIGTERM" : "SIGINT"));
switch (signum) {
case SIGTERM: LOG_INFO_NOLOCK("===== Stopping by SIGTERM ====="); break;
case SIGINT: LOG_INFO_NOLOCK("===== Stopping by SIGINT ====="); break;
default: LOG_INFO_NOLOCK("===== Stopping by %d =====", signum); break;
}
stream_loop_break(_ctx->stream);
http_server_loop_break(_ctx->server);
server_loop_break(_ctx->server);
}
static void _install_signal_handlers(void) {
struct sigaction sig_act;
MEMSET_ZERO(sig_act);
assert(!sigemptyset(&sig_act.sa_mask));
sig_act.sa_handler = _signal_handler;
assert(!sigaddset(&sig_act.sa_mask, SIGINT));
assert(!sigaddset(&sig_act.sa_mask, SIGTERM));
LOG_INFO("Installing SIGINT handler ...");
LOG_DEBUG("Installing SIGINT handler ...");
assert(!sigaction(SIGINT, &sig_act, NULL));
LOG_INFO("Installing SIGTERM handler ...");
LOG_DEBUG("Installing SIGTERM handler ...");
assert(!sigaction(SIGTERM, &sig_act, NULL));
LOG_INFO("Ignoring SIGPIPE ...");
LOG_DEBUG("Ignoring SIGPIPE ...");
assert(signal(SIGPIPE, SIG_IGN) != SIG_ERR);
}
int main(int argc, char *argv[]) {
struct options_t *options;
struct device_t *dev;
struct encoder_t *encoder;
struct stream_t *stream;
struct http_server_t *server;
assert(argc >= 0);
int exit_code = 0;
LOGGING_INIT;
A_THREAD_RENAME("main");
options = options_init(argc, argv);
# ifdef WITH_GPIO
GPIO_INIT;
options_s *options = options_init(argc, argv);
device_s *dev = device_init();
encoder_s *enc = encoder_init();
stream_s *stream = stream_init(dev, enc);
server_s *server = server_init(stream);
# ifdef WITH_OMX
bool i_bcm_host = false;
OMX_ERRORTYPE omx_error = OMX_ErrorUndefined;
# endif
dev = device_init();
encoder = encoder_init();
stream = stream_init(dev, encoder);
server = http_server_init(stream);
if ((exit_code = options_parse(options, dev, enc, stream, server)) == 0) {
# ifdef WITH_OMX
if (enc->type == ENCODER_TYPE_OMX || stream->h264_sink) {
bcm_host_init();
i_bcm_host = true;
}
if (enc->type == ENCODER_TYPE_OMX) {
if ((omx_error = OMX_Init()) != OMX_ErrorNone) {
LOG_ERROR_OMX(omx_error, "Can't initialize OMX Core; forced CPU encoder");
enc->type = ENCODER_TYPE_CPU;
}
}
# endif
if ((exit_code = options_parse(options, dev, encoder, server)) == 0) {
# ifdef WITH_GPIO
GPIO_INIT_PINOUT;
gpio_init();
# endif
_install_signal_handlers();
pthread_t stream_loop_tid;
pthread_t server_loop_tid;
struct _main_context_t ctx;
_main_context_s ctx;
ctx.stream = stream;
ctx.server = server;
_ctx = &ctx;
if ((exit_code = http_server_listen(server)) == 0) {
if ((exit_code = server_listen(server)) == 0) {
# ifdef WITH_GPIO
GPIO_SET_HIGH(prog_running);
gpio_set_prog_running(true);
# endif
pthread_t stream_loop_tid;
pthread_t server_loop_tid;
A_THREAD_CREATE(&stream_loop_tid, _stream_loop_thread, NULL);
A_THREAD_CREATE(&server_loop_tid, _server_loop_thread, NULL);
A_THREAD_JOIN(server_loop_tid);
A_THREAD_JOIN(stream_loop_tid);
}
# ifdef WITH_GPIO
gpio_set_prog_running(false);
gpio_destroy();
# endif
}
http_server_destroy(server);
server_destroy(server);
stream_destroy(stream);
encoder_destroy(encoder);
encoder_destroy(enc);
device_destroy(dev);
options_destroy(options);
# ifdef WITH_GPIO
GPIO_SET_LOW(prog_running);
# ifdef WITH_OMX
if (omx_error == OMX_ErrorNone) {
OMX_Deinit();
}
if (i_bcm_host) {
bcm_host_deinit();
}
# endif
options_destroy(options);
if (exit_code == 0) {
LOG_INFO("Bye-bye");
}

717
src/ustreamer/options.c Normal file
View File

@@ -0,0 +1,717 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 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"
enum _OPT_VALUES {
_O_DEVICE = 'd',
_O_INPUT = 'i',
_O_RESOLUTION = 'r',
_O_FORMAT = 'm',
_O_TV_STANDARD = 'a',
_O_IO_METHOD = 'I',
_O_DESIRED_FPS = 'f',
_O_MIN_FRAME_SIZE = 'z',
_O_PERSISTENT = 'n',
_O_DV_TIMINGS = 't',
_O_BUFFERS = 'b',
_O_WORKERS = 'w',
_O_QUALITY = 'q',
_O_ENCODER = 'c',
# ifdef WITH_OMX
_O_GLITCHED_RESOLUTIONS = 'g',
# endif
_O_BLANK = 'k',
_O_LAST_AS_BLANK = 'K',
_O_SLOWDOWN = 'l',
_O_HOST = 's',
_O_PORT = 'p',
_O_UNIX = 'U',
_O_UNIX_RM = 'D',
_O_UNIX_MODE = 'M',
_O_DROP_SAME_FRAMES = 'e',
_O_FAKE_RESOLUTION = 'R',
_O_HELP = 'h',
_O_VERSION = 'v',
// Longs only
_O_DEVICE_TIMEOUT = 10000,
_O_DEVICE_ERROR_DELAY,
_O_IMAGE_DEFAULT,
_O_BRIGHTNESS,
_O_CONTRAST,
_O_SATURATION,
_O_HUE,
_O_GAMMA,
_O_SHARPNESS,
_O_BACKLIGHT_COMPENSATION,
_O_WHITE_BALANCE,
_O_GAIN,
_O_COLOR_EFFECT,
_O_FLIP_VERTICAL,
_O_FLIP_HORIZONTAL,
_O_USER,
_O_PASSWD,
_O_STATIC,
_O_ALLOW_ORIGIN,
_O_TCP_NODELAY,
_O_SERVER_TIMEOUT,
# define ADD_SINK(_prefix) \
_O_##_prefix, \
_O_##_prefix##_MODE, \
_O_##_prefix##_RM, \
_O_##_prefix##_CLIENT_TTL, \
_O_##_prefix##_TIMEOUT,
ADD_SINK(SINK)
ADD_SINK(RAW_SINK)
# ifdef WITH_OMX
ADD_SINK(H264_SINK)
_O_H264_BITRATE,
_O_H264_GOP,
# endif
# undef ADD_SINK
# ifdef WITH_GPIO
_O_GPIO_DEVICE,
_O_GPIO_CONSUMER_PREFIX,
_O_GPIO_PROG_RUNNING,
_O_GPIO_STREAM_ONLINE,
_O_GPIO_HAS_HTTP_CLIENTS,
# endif
# ifdef HAS_PDEATHSIG
_O_EXIT_ON_PARENT_DEATH,
# endif
# ifdef WITH_SETPROCTITLE
_O_PROCESS_NAME_PREFIX,
# endif
_O_NOTIFY_PARENT,
_O_LOG_LEVEL,
_O_PERF,
_O_VERBOSE,
_O_DEBUG,
_O_FORCE_LOG_COLORS,
_O_NO_LOG_COLORS,
_O_FEATURES,
};
static const struct option _LONG_OPTS[] = {
{"device", required_argument, NULL, _O_DEVICE},
{"input", required_argument, NULL, _O_INPUT},
{"resolution", required_argument, NULL, _O_RESOLUTION},
{"format", required_argument, NULL, _O_FORMAT},
{"tv-standard", required_argument, NULL, _O_TV_STANDARD},
{"io-method", required_argument, NULL, _O_IO_METHOD},
{"desired-fps", required_argument, NULL, _O_DESIRED_FPS},
{"min-frame-size", required_argument, NULL, _O_MIN_FRAME_SIZE},
{"persistent", no_argument, NULL, _O_PERSISTENT},
{"dv-timings", no_argument, NULL, _O_DV_TIMINGS},
{"buffers", required_argument, NULL, _O_BUFFERS},
{"workers", required_argument, NULL, _O_WORKERS},
{"quality", required_argument, NULL, _O_QUALITY},
{"encoder", required_argument, NULL, _O_ENCODER},
# ifdef WITH_OMX
{"glitched-resolutions", required_argument, NULL, _O_GLITCHED_RESOLUTIONS},
# endif
{"blank", required_argument, NULL, _O_BLANK},
{"last-as-blank", required_argument, NULL, _O_LAST_AS_BLANK},
{"slowdown", no_argument, NULL, _O_SLOWDOWN},
{"device-timeout", required_argument, NULL, _O_DEVICE_TIMEOUT},
{"device-error-delay", required_argument, NULL, _O_DEVICE_ERROR_DELAY},
{"image-default", no_argument, NULL, _O_IMAGE_DEFAULT},
{"brightness", required_argument, NULL, _O_BRIGHTNESS},
{"contrast", required_argument, NULL, _O_CONTRAST},
{"saturation", required_argument, NULL, _O_SATURATION},
{"hue", required_argument, NULL, _O_HUE},
{"gamma", required_argument, NULL, _O_GAMMA},
{"sharpness", required_argument, NULL, _O_SHARPNESS},
{"backlight-compensation", required_argument, NULL, _O_BACKLIGHT_COMPENSATION},
{"white-balance", required_argument, NULL, _O_WHITE_BALANCE},
{"gain", required_argument, NULL, _O_GAIN},
{"color-effect", required_argument, NULL, _O_COLOR_EFFECT},
{"flip-vertical", required_argument, NULL, _O_FLIP_VERTICAL},
{"flip-horizontal", required_argument, NULL, _O_FLIP_HORIZONTAL},
{"host", required_argument, NULL, _O_HOST},
{"port", required_argument, NULL, _O_PORT},
{"unix", required_argument, NULL, _O_UNIX},
{"unix-rm", no_argument, NULL, _O_UNIX_RM},
{"unix-mode", required_argument, NULL, _O_UNIX_MODE},
{"user", required_argument, NULL, _O_USER},
{"passwd", required_argument, NULL, _O_PASSWD},
{"static", required_argument, NULL, _O_STATIC},
{"drop-same-frames", required_argument, NULL, _O_DROP_SAME_FRAMES},
{"allow-origin", required_argument, NULL, _O_ALLOW_ORIGIN},
{"fake-resolution", required_argument, NULL, _O_FAKE_RESOLUTION},
{"tcp-nodelay", no_argument, NULL, _O_TCP_NODELAY},
{"server-timeout", required_argument, NULL, _O_SERVER_TIMEOUT},
# define ADD_SINK(_opt, _prefix) \
{_opt "sink", required_argument, NULL, _O_##_prefix}, \
{_opt "sink-mode", required_argument, NULL, _O_##_prefix##_MODE}, \
{_opt "sink-rm", no_argument, NULL, _O_##_prefix##_RM}, \
{_opt "sink-client-ttl", required_argument, NULL, _O_##_prefix##_CLIENT_TTL}, \
{_opt "sink-timeout", required_argument, NULL, _O_##_prefix##_TIMEOUT},
ADD_SINK("", SINK)
ADD_SINK("raw-", RAW_SINK)
# ifdef WITH_OMX
ADD_SINK("h264-", H264_SINK)
{"h264-bitrate", required_argument, NULL, _O_H264_BITRATE},
{"h264-gop", required_argument, NULL, _O_H264_GOP},
# endif
# undef ADD_SINK
# ifdef WITH_GPIO
{"gpio-device", required_argument, NULL, _O_GPIO_DEVICE},
{"gpio-consumer-prefix", required_argument, NULL, _O_GPIO_CONSUMER_PREFIX},
{"gpio-prog-running", required_argument, NULL, _O_GPIO_PROG_RUNNING},
{"gpio-stream-online", required_argument, NULL, _O_GPIO_STREAM_ONLINE},
{"gpio-has-http-clients", required_argument, NULL, _O_GPIO_HAS_HTTP_CLIENTS},
# endif
# ifdef HAS_PDEATHSIG
{"exit-on-parent-death", no_argument, NULL, _O_EXIT_ON_PARENT_DEATH},
# endif
# ifdef WITH_SETPROCTITLE
{"process-name-prefix", required_argument, NULL, _O_PROCESS_NAME_PREFIX},
# endif
{"notify-parent", no_argument, NULL, _O_NOTIFY_PARENT},
{"log-level", required_argument, NULL, _O_LOG_LEVEL},
{"perf", no_argument, NULL, _O_PERF},
{"verbose", no_argument, NULL, _O_VERBOSE},
{"debug", no_argument, NULL, _O_DEBUG},
{"force-log-colors", no_argument, NULL, _O_FORCE_LOG_COLORS},
{"no-log-colors", no_argument, NULL, _O_NO_LOG_COLORS},
{"help", no_argument, NULL, _O_HELP},
{"version", no_argument, NULL, _O_VERSION},
{"features", no_argument, NULL, _O_FEATURES},
{NULL, 0, NULL, 0},
};
static int _parse_resolution(const char *str, unsigned *width, unsigned *height, bool limited);
static void _features(void);
static void _help(FILE *fp, device_s *dev, encoder_s *enc, stream_s *stream, server_s *server);
options_s *options_init(unsigned argc, char *argv[]) {
options_s *options;
A_CALLOC(options, 1);
options->argc = argc;
options->argv = argv;
A_CALLOC(options->argv_copy, argc);
for (unsigned index = 0; index < argc; ++index) {
assert(options->argv_copy[index] = strdup(argv[index]));
}
return options;
}
void options_destroy(options_s *options) {
# define ADD_SINK(_prefix) { \
if (options->_prefix) { \
memsink_destroy(options->_prefix); \
} \
}
ADD_SINK(sink);
ADD_SINK(raw_sink);
# ifdef WITH_OMX
ADD_SINK(h264_sink);
# endif
# undef ADD_SINK
if (options->blank) {
frame_destroy(options->blank);
}
for (unsigned index = 0; index < options->argc; ++index) {
free(options->argv_copy[index]);
}
free(options->argv_copy);
free(options);
}
int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *stream, server_s *server) {
# define OPT_SET(_dest, _value) { \
_dest = _value; \
break; \
}
# define OPT_NUMBER(_name, _dest, _min, _max, _base) { \
errno = 0; char *_end = NULL; long long _tmp = strtoll(optarg, &_end, _base); \
if (errno || *_end || _tmp < _min || _tmp > _max) { \
printf("Invalid value for '%s=%s': min=%lld, max=%lld\n", _name, optarg, (long long)_min, (long long)_max); \
return -1; \
} \
_dest = _tmp; \
break; \
}
# define OPT_RESOLUTION(_name, _dest_width, _dest_height, _limited) { \
switch (_parse_resolution(optarg, &_dest_width, &_dest_height, _limited)) { \
case -1: \
printf("Invalid resolution format for '%s=%s'\n", _name, optarg); \
return -1; \
case -2: \
printf("Invalid width of '%s=%s': min=%u, max=%u\n", _name, optarg, VIDEO_MIN_WIDTH, VIDEO_MAX_WIDTH); \
return -1; \
case -3: \
printf("Invalid height of '%s=%s': min=%u, max=%u\n", _name, optarg, VIDEO_MIN_HEIGHT, VIDEO_MAX_HEIGHT); \
return -1; \
case 0: break; \
default: assert(0 && "Unknown error"); \
} \
break; \
}
# define OPT_PARSE(_name, _dest, _func, _invalid, _available) { \
if ((_dest = _func(optarg)) == _invalid) { \
printf("Unknown " _name ": %s; available: %s\n", optarg, _available); \
return -1; \
} \
break; \
}
# define OPT_CTL_DEFAULT_NOBREAK(_dest) { \
dev->ctl._dest.mode = CTL_MODE_DEFAULT; \
}
# define OPT_CTL_MANUAL(_dest) { \
if (!strcasecmp(optarg, "default")) { \
OPT_CTL_DEFAULT_NOBREAK(_dest); \
} else { \
dev->ctl._dest.mode = CTL_MODE_VALUE; \
OPT_NUMBER("--"#_dest, dev->ctl._dest.value, INT_MIN, INT_MAX, 0); \
} \
break; \
}
# define OPT_CTL_AUTO(_dest) { \
if (!strcasecmp(optarg, "default")) { \
OPT_CTL_DEFAULT_NOBREAK(_dest); \
} else if (!strcasecmp(optarg, "auto")) { \
dev->ctl._dest.mode = CTL_MODE_AUTO; \
} else { \
dev->ctl._dest.mode = CTL_MODE_VALUE; \
OPT_NUMBER("--"#_dest, dev->ctl._dest.value, INT_MIN, INT_MAX, 0); \
} \
break; \
}
char *blank_path = NULL;
# define ADD_SINK(_prefix) \
char *_prefix##_name = NULL; \
mode_t _prefix##_mode = 0660; \
bool _prefix##_rm = false; \
unsigned _prefix##_client_ttl = 10; \
unsigned _prefix##_timeout = 1;
ADD_SINK(sink);
ADD_SINK(raw_sink);
# ifdef WITH_OMX
ADD_SINK(h264_sink);
# endif
# undef ADD_SINK
# ifdef WITH_SETPROCTITLE
char *process_name_prefix = NULL;
# endif
char short_opts[128];
build_short_options(_LONG_OPTS, short_opts, 128);
for (int ch; (ch = getopt_long(options->argc, options->argv_copy, short_opts, _LONG_OPTS, NULL)) >= 0;) {
switch (ch) {
case _O_DEVICE: OPT_SET(dev->path, optarg);
case _O_INPUT: OPT_NUMBER("--input", dev->input, 0, 128, 0);
case _O_RESOLUTION: OPT_RESOLUTION("--resolution", dev->width, dev->height, true);
# pragma GCC diagnostic ignored "-Wsign-compare"
# pragma GCC diagnostic push
case _O_FORMAT: OPT_PARSE("pixel format", dev->format, device_parse_format, FORMAT_UNKNOWN, FORMATS_STR);
# pragma GCC diagnostic pop
case _O_TV_STANDARD: OPT_PARSE("TV standard", dev->standard, device_parse_standard, STANDARD_UNKNOWN, STANDARDS_STR);
case _O_IO_METHOD: OPT_PARSE("IO method", dev->io_method, device_parse_io_method, IO_METHOD_UNKNOWN, IO_METHODS_STR);
case _O_DESIRED_FPS: OPT_NUMBER("--desired-fps", dev->desired_fps, 0, VIDEO_MAX_FPS, 0);
case _O_MIN_FRAME_SIZE: OPT_NUMBER("--min-frame-size", dev->min_frame_size, 1, 8192, 0);
case _O_PERSISTENT: OPT_SET(dev->persistent, true);
case _O_DV_TIMINGS: OPT_SET(dev->dv_timings, true);
case _O_BUFFERS: OPT_NUMBER("--buffers", dev->n_bufs, 1, 32, 0);
case _O_WORKERS: OPT_NUMBER("--workers", enc->n_workers, 1, 32, 0);
case _O_QUALITY: OPT_NUMBER("--quality", dev->jpeg_quality, 1, 100, 0);
case _O_ENCODER: OPT_PARSE("encoder type", enc->type, encoder_parse_type, ENCODER_TYPE_UNKNOWN, ENCODER_TYPES_STR);
# ifdef WITH_OMX
case _O_GLITCHED_RESOLUTIONS: break;
# endif
case _O_BLANK: OPT_SET(blank_path, optarg);
case _O_LAST_AS_BLANK: OPT_NUMBER("--last-as-blank", stream->last_as_blank, 0, 86400, 0);
case _O_SLOWDOWN: OPT_SET(stream->slowdown, true);
case _O_DEVICE_TIMEOUT: OPT_NUMBER("--device-timeout", dev->timeout, 1, 60, 0);
case _O_DEVICE_ERROR_DELAY: OPT_NUMBER("--device-error-delay", stream->error_delay, 1, 60, 0);
case _O_IMAGE_DEFAULT:
OPT_CTL_DEFAULT_NOBREAK(brightness);
OPT_CTL_DEFAULT_NOBREAK(contrast);
OPT_CTL_DEFAULT_NOBREAK(saturation);
OPT_CTL_DEFAULT_NOBREAK(hue);
OPT_CTL_DEFAULT_NOBREAK(gamma);
OPT_CTL_DEFAULT_NOBREAK(sharpness);
OPT_CTL_DEFAULT_NOBREAK(backlight_compensation);
OPT_CTL_DEFAULT_NOBREAK(white_balance);
OPT_CTL_DEFAULT_NOBREAK(gain);
OPT_CTL_DEFAULT_NOBREAK(color_effect);
OPT_CTL_DEFAULT_NOBREAK(flip_vertical);
OPT_CTL_DEFAULT_NOBREAK(flip_horizontal);
break;
case _O_BRIGHTNESS: OPT_CTL_AUTO(brightness);
case _O_CONTRAST: OPT_CTL_MANUAL(contrast);
case _O_SATURATION: OPT_CTL_MANUAL(saturation);
case _O_HUE: OPT_CTL_AUTO(hue);
case _O_GAMMA: OPT_CTL_MANUAL(gamma);
case _O_SHARPNESS: OPT_CTL_MANUAL(sharpness);
case _O_BACKLIGHT_COMPENSATION: OPT_CTL_MANUAL(backlight_compensation);
case _O_WHITE_BALANCE: OPT_CTL_AUTO(white_balance);
case _O_GAIN: OPT_CTL_AUTO(gain);
case _O_COLOR_EFFECT: OPT_CTL_MANUAL(color_effect);
case _O_FLIP_VERTICAL: OPT_CTL_MANUAL(flip_vertical);
case _O_FLIP_HORIZONTAL: OPT_CTL_MANUAL(flip_horizontal);
case _O_HOST: OPT_SET(server->host, optarg);
case _O_PORT: OPT_NUMBER("--port", server->port, 1, 65535, 0);
case _O_UNIX: OPT_SET(server->unix_path, optarg);
case _O_UNIX_RM: OPT_SET(server->unix_rm, true);
case _O_UNIX_MODE: OPT_NUMBER("--unix-mode", server->unix_mode, INT_MIN, INT_MAX, 8);
case _O_USER: OPT_SET(server->user, optarg);
case _O_PASSWD: OPT_SET(server->passwd, optarg);
case _O_STATIC: OPT_SET(server->static_path, optarg);
case _O_DROP_SAME_FRAMES: OPT_NUMBER("--drop-same-frames", server->drop_same_frames, 0, VIDEO_MAX_FPS, 0);
case _O_FAKE_RESOLUTION: OPT_RESOLUTION("--fake-resolution", server->fake_width, server->fake_height, false);
case _O_ALLOW_ORIGIN: OPT_SET(server->allow_origin, optarg);
case _O_TCP_NODELAY: OPT_SET(server->tcp_nodelay, true);
case _O_SERVER_TIMEOUT: OPT_NUMBER("--server-timeout", server->timeout, 1, 60, 0);
# define ADD_SINK(_opt, _lp, _up) \
case _O_##_up: OPT_SET(_lp##_name, optarg); \
case _O_##_up##_MODE: OPT_NUMBER("--" #_opt "sink-mode", _lp##_mode, INT_MIN, INT_MAX, 8); \
case _O_##_up##_RM: OPT_SET(_lp##_rm, true); \
case _O_##_up##_CLIENT_TTL: OPT_NUMBER("--" #_opt "sink-client-ttl", _lp##_client_ttl, 1, 60, 0); \
case _O_##_up##_TIMEOUT: OPT_NUMBER("--" #_opt "sink-timeout", _lp##_timeout, 1, 60, 0);
ADD_SINK("", sink, SINK)
ADD_SINK("raw-", raw_sink, RAW_SINK)
# ifdef WITH_OMX
ADD_SINK("h264-", h264_sink, H264_SINK)
case _O_H264_BITRATE: OPT_NUMBER("--h264-bitrate", stream->h264_bitrate, 100, 16000, 0);
case _O_H264_GOP: OPT_NUMBER("--h264-gop", stream->h264_gop, 0, 60, 0);
# endif
# undef ADD_SINK
# ifdef WITH_GPIO
case _O_GPIO_DEVICE: OPT_SET(gpio.path, optarg);
case _O_GPIO_CONSUMER_PREFIX: OPT_SET(gpio.consumer_prefix, optarg);
case _O_GPIO_PROG_RUNNING: OPT_NUMBER("--gpio-prog-running", gpio.prog_running.pin, 0, 256, 0);
case _O_GPIO_STREAM_ONLINE: OPT_NUMBER("--gpio-stream-online", gpio.stream_online.pin, 0, 256, 0);
case _O_GPIO_HAS_HTTP_CLIENTS: OPT_NUMBER("--gpio-has-http-clients", gpio.has_http_clients.pin, 0, 256, 0);
# endif
# ifdef HAS_PDEATHSIG
case _O_EXIT_ON_PARENT_DEATH:
if (process_track_parent_death() < 0) {
return -1;
};
break;
# endif
# ifdef WITH_SETPROCTITLE
case _O_PROCESS_NAME_PREFIX: OPT_SET(process_name_prefix, optarg);
# endif
case _O_NOTIFY_PARENT: OPT_SET(server->notify_parent, true);
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_HELP: _help(stdout, dev, enc, stream, server); return 1;
case _O_VERSION: puts(VERSION); return 1;
case _O_FEATURES: _features(); return 1;
case 0: break;
default: return -1;
}
}
options->blank = blank_frame_init(blank_path);
stream->blank = options->blank;
# define ADD_SINK(_label, _prefix) { \
if (_prefix##_name && _prefix##_name[0] != '\0') { \
options->_prefix = memsink_init( \
_label, \
_prefix##_name, \
true, \
_prefix##_mode, \
_prefix##_rm, \
_prefix##_client_ttl, \
_prefix##_timeout \
); \
} \
stream->_prefix = options->_prefix; \
}
ADD_SINK("JPEG", sink);
ADD_SINK("RAW", raw_sink);
# ifdef WITH_OMX
ADD_SINK("H264", h264_sink);
# endif
# undef ADD_SINK
# ifdef WITH_SETPROCTITLE
if (process_name_prefix != NULL) {
process_set_name_prefix(options->argc, options->argv, process_name_prefix);
}
# endif
# undef OPT_CTL_AUTO
# undef OPT_CTL_MANUAL
# undef OPT_CTL_DEFAULT_NOBREAK
# undef OPT_PARSE
# undef OPT_RESOLUTION
# undef OPT_NUMBER
# undef OPT_SET
return 0;
}
static int _parse_resolution(const char *str, unsigned *width, unsigned *height, bool limited) {
unsigned tmp_width;
unsigned tmp_height;
if (sscanf(str, "%ux%u", &tmp_width, &tmp_height) != 2) {
return -1;
}
if (limited) {
if (tmp_width < VIDEO_MIN_WIDTH || tmp_width > VIDEO_MAX_WIDTH) {
return -2;
}
if (tmp_height < VIDEO_MIN_HEIGHT || tmp_height > VIDEO_MAX_HEIGHT) {
return -3;
}
}
*width = tmp_width;
*height = tmp_height;
return 0;
}
static void _features(void) {
# ifdef WITH_OMX
puts("+ WITH_OMX");
# else
puts("- WITH_OMX");
# endif
# ifdef WITH_GPIO
puts("+ WITH_GPIO");
# else
puts("- WITH_GPIO");
# endif
# ifdef WITH_PTHREAD_NP
puts("+ WITH_PTHREAD_NP");
# else
puts("- WITH_PTHREAD_NP");
# endif
# ifdef WITH_SETPROCTITLE
puts("+ WITH_SETPROCTITLE");
# else
puts("- WITH_SETPROCTITLE");
# endif
# ifdef HAS_PDEATHSIG
puts("+ HAS_PDEATHSIG");
# else
puts("- HAS_PDEATHSIG");
# endif
}
static void _help(FILE *fp, device_s *dev, encoder_s *enc, stream_s *stream, server_s *server) {
# define SAY(_msg, ...) fprintf(fp, _msg "\n", ##__VA_ARGS__)
SAY("\nuStreamer - Lightweight and fast MJPG-HTTP streamer");
SAY("═══════════════════════════════════════════════════");
SAY("Version: %s; license: GPLv3", VERSION);
SAY("Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com>\n");
SAY("Capturing options:");
SAY("══════════════════");
SAY(" -d|--device </dev/path> ───────────── Path to V4L2 device. Default: %s.\n", dev->path);
SAY(" -i|--input <N> ────────────────────── Input channel. Default: %u.\n", dev->input);
SAY(" -r|--resolution <WxH> ─────────────── Initial image resolution. Default: %ux%u.\n", dev->width, dev->height);
SAY(" -m|--format <fmt> ─────────────────── Image format.");
SAY(" Available: %s; default: YUYV.\n", FORMATS_STR);
SAY(" -a|--tv-standard <std> ────────────── Force TV standard.");
SAY(" Available: %s; default: disabled.\n", STANDARDS_STR);
SAY(" -I|--io-method <method> ───────────── Set V4L2 IO method (see kernel documentation).");
SAY(" Changing of this parameter may increase the performance. Or not.");
SAY(" Available: %s; default: MMAP.\n", IO_METHODS_STR);
SAY(" -f|--desired-fps <N> ──────────────── Desired FPS. Default: maximum possible.\n");
SAY(" -z|--min-frame-size <N> ───────────── Drop frames smaller then this limit. Useful if the device");
SAY(" produces small-sized garbage frames. Default: %zu bytes.\n", dev->min_frame_size);
SAY(" -n|--persistent ───────────────────── Don't re-initialize device on timeout. Default: disabled.\n");
SAY(" -t|--dv-timings ───────────────────── Enable DV timings querying and events processing");
SAY(" to automatic resolution change. Default: disabled.\n");
SAY(" -b|--buffers <N> ──────────────────── The number of buffers to receive data from the device.");
SAY(" Each buffer may processed using an independent thread.");
SAY(" Default: %u (the number of CPU cores (but not more than 4) + 1).\n", dev->n_bufs);
SAY(" -w|--workers <N> ──────────────────── The number of worker threads but not more than buffers.");
SAY(" Default: %u (the number of CPU cores (but not more than 4)).\n", enc->n_workers);
SAY(" -q|--quality <N> ──────────────────── Set quality of JPEG encoding from 1 to 100 (best). Default: %u.", dev->jpeg_quality);
SAY(" Note: If HW encoding is used (JPEG source format selected),");
SAY(" this parameter attempts to configure the camera");
SAY(" or capture device hardware's internal encoder.");
SAY(" It does not re-encode MJPG to MJPG to change the quality level");
SAY(" for sources that already output MJPG.\n");
SAY(" -c|--encoder <type> ───────────────── Use specified encoder. It may affect the number of workers.");
SAY(" Available:");
SAY(" * CPU ── Software MJPG encoding (default);");
# ifdef WITH_OMX
SAY(" * OMX ── GPU hardware accelerated MJPG encoding with OpenMax;");
# endif
SAY(" * HW ─── Use pre-encoded MJPG frames directly from camera hardware.");
SAY(" * NOOP ─ Don't compress MJPG stream (do nothing).\n");
# ifdef WITH_OMX
SAY(" -g|--glitched-resolutions <WxH,...> ─ It doesn't do anything. Still here for compatibility.\n");
# endif
SAY(" -k|--blank <path> ─────────────────── Path to JPEG file that will be shown when the device is disconnected");
SAY(" during the streaming. Default: black screen 640x480 with 'NO SIGNAL'.\n");
SAY(" -K|--last-as-blank <sec> ──────────── Show the last frame received from the camera after it was disconnected,");
SAY(" but no more than specified time (or endlessly if 0 is specified).");
SAY(" If the device has not yet been online, display 'NO SIGNAL' or the image");
SAY(" specified by option --blank. Default: disabled.\n");
SAY(" -l|--slowdown ─────────────────────── Slowdown capturing to 1 FPS or less when no stream or sink clients");
SAY(" are connected. Useful to reduce CPU consumption. Default: disabled.\n");
SAY(" --device-timeout <sec> ────────────── Timeout for device querying. Default: %u.\n", dev->timeout);
SAY(" --device-error-delay <sec> ────────── Delay before trying to connect to the device again");
SAY(" after an error (timeout for example). Default: %u.\n", stream->error_delay);
SAY("Image control options:");
SAY("══════════════════════");
SAY(" --image-default ────────────────────── Reset all image settings below to default. Default: no change.\n");
SAY(" --brightness <N|auto|default> ──────── Set brightness. Default: no change.\n");
SAY(" --contrast <N|default> ─────────────── Set contrast. Default: no change.\n");
SAY(" --saturation <N|default> ───────────── Set saturation. Default: no change.\n");
SAY(" --hue <N|auto|default> ─────────────── Set hue. Default: no change.\n");
SAY(" --gamma <N|default> ─────────────────── Set gamma. Default: no change.\n");
SAY(" --sharpness <N|default> ────────────── Set sharpness. Default: no change.\n");
SAY(" --backlight-compensation <N|default> ─ Set backlight compensation. Default: no change.\n");
SAY(" --white-balance <N|auto|default> ───── Set white balance. Default: no change.\n");
SAY(" --gain <N|auto|default> ────────────── Set gain. Default: no change.\n");
SAY(" --color-effect <N|default> ─────────── Set color effect. Default: no change.\n");
SAY(" --flip-vertical <1|0|default> ──────── Set vertical flip. Default: no change.\n");
SAY(" --flip-horizontal <1|0|default> ────── Set horizontal flip. Default: no change.\n");
SAY(" Hint: use v4l2-ctl --list-ctrls-menus to query available controls of the device.\n");
SAY("HTTP server options:");
SAY("════════════════════");
SAY(" -s|--host <address> ──────── Listen on Hostname or IP. Default: %s.\n", server->host);
SAY(" -p|--port <N> ────────────── Bind to this TCP port. Default: %u.\n", server->port);
SAY(" -U|--unix <path> ─────────── Bind to UNIX domain socket. Default: disabled.\n");
SAY(" -D|--unix-rm ─────────────── Try to remove old UNIX socket file before binding. Default: disabled.\n");
SAY(" -M|--unix-mode <mode> ────── Set UNIX socket file permissions (like 777). Default: disabled.\n");
SAY(" --user <name> ────────────── HTTP basic auth user. Default: disabled.\n");
SAY(" --passwd <str> ───────────── HTTP basic auth passwd. Default: empty.\n");
SAY(" --static <path> ───────────── Path to dir with static files instead of embedded root index page.");
SAY(" Symlinks are not supported for security reasons. Default: disabled.\n");
SAY(" -e|--drop-same-frames <N> ── Don't send identical frames to clients, but no more than specified number.");
SAY(" It can significantly reduce the outgoing traffic, but will increase");
SAY(" the CPU loading. Don't use this option with analog signal sources");
SAY(" or webcams, it's useless. Default: disabled.\n");
SAY(" -R|--fake-resolution <WxH> ─ Override image resolution for the /state. Default: disabled.\n");
SAY(" --tcp-nodelay ────────────── Set TCP_NODELAY flag to the client /stream socket. Ignored for --unix.");
SAY(" Default: disabled.\n");
SAY(" --allow-origin <str> ─────── Set Access-Control-Allow-Origin header. Default: disabled.\n");
SAY(" --server-timeout <sec> ───── Timeout for client connections. Default: %u.\n", server->timeout);
# define ADD_SINK(_name, _opt) \
SAY(_name " sink options:"); \
SAY("══════════════════"); \
SAY(" --" _opt "sink <name> ─────────── Use the shared memory to sink " _name " frames. Default: disabled.\n"); \
SAY(" --" _opt "sink-mode <mode> ────── Set " _name " sink permissions (like 777). Default: 660.\n"); \
SAY(" --" _opt "sink-rm ─────────────── Remove shared memory on stop. Default: disabled.\n"); \
SAY(" --" _opt "sink-client-ttl <sec> ─ Client TTL. Default: 10.\n"); \
SAY(" --" _opt "sink-timeout <sec> ──── Timeout for lock. Default: 1.\n");
ADD_SINK("JPEG", "")
ADD_SINK("RAW", "raw-")
# ifdef WITH_OMX
ADD_SINK("H264", "h264-")
SAY(" --h264-bitrate <kbps> ──────── H264 bitrate in Kbps. Default: %u.\n", stream->h264_bitrate);
SAY(" --h264-gop <N> ─────────────── Intarval between keyframes. Default: %u.\n", stream->h264_gop);
# endif
# undef ADD_SINK
# ifdef WITH_GPIO
SAY("GPIO options:");
SAY("═════════════");
SAY(" --gpio-device </dev/path> ───── Path to GPIO character device. Default: %s.\n", gpio.path);
SAY(" --gpio-consumer-prefix <str> ── Consumer prefix for GPIO outputs. Default: %s.\n", gpio.consumer_prefix);
SAY(" --gpio-prog-running <pin> ───── Set 1 on GPIO pin while uStreamer is running. Default: disabled.\n");
SAY(" --gpio-stream-online <pin> ──── Set 1 while streaming. Default: disabled.\n");
SAY(" --gpio-has-http-clients <pin> ─ Set 1 while stream has at least one client. Default: disabled.\n");
# endif
# if (defined(HAS_PDEATHSIG) || defined(WITH_SETPROCTITLE))
SAY("Process options:");
SAY("════════════════");
# endif
# ifdef HAS_PDEATHSIG
SAY(" --exit-on-parent-death ─────── Exit the program if the parent process is dead. Default: disabled.\n");
# endif
# ifdef WITH_SETPROCTITLE
SAY(" --process-name-prefix <str> ── Set process name prefix which will be displayed in the process list");
SAY(" like '<str>: ustreamer --blah-blah-blah'. Default: disabled.\n");
SAY(" --notify-parent ────────────── Send SIGUSR2 to the parent process when the stream parameters are changed.");
SAY(" Checking changes is performed for the online flag and image resolution.\n");
# endif
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(" --perf ───────────── Enable performance messages (same as --log-level=1). Default: disabled.\n");
SAY(" --verbose ────────── Enable verbose messages and lower (same as --log-level=2). Default: disabled.\n");
SAY(" --debug ──────────── Enable debug messages and lower (same as --log-level=3). Default: disabled.\n");
SAY(" --force-log-colors ─ Force color logging. Default: colored if stderr is a TTY.\n");
SAY(" --no-log-colors ──── Disable color logging. Default: ditto.\n");
SAY("Help options:");
SAY("═════════════");
SAY(" -h|--help ─────── Print this text and exit.\n");
SAY(" -v|--version ──── Print version and exit.\n");
SAY(" --features ────── Print list of supported features.\n");
# undef SAY
}

68
src/ustreamer/options.h Normal file
View File

@@ -0,0 +1,68 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 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 <string.h>
#include <strings.h>
#include <limits.h>
#include <getopt.h>
#include <errno.h>
#include <assert.h>
#include "../libs/config.h"
#include "../libs/logging.h"
#include "../libs/process.h"
#include "../libs/frame.h"
#include "../libs/memsink.h"
#include "../libs/options.h"
#include "device.h"
#include "encoder.h"
#include "blank.h"
#include "stream.h"
#include "http/server.h"
#ifdef WITH_GPIO
# include "gpio/gpio.h"
#endif
typedef struct {
unsigned argc;
char **argv;
char **argv_copy;
frame_s *blank;
memsink_s *sink;
memsink_s *raw_sink;
# ifdef WITH_OMX
memsink_s *h264_sink;
# endif
} options_s;
options_s *options_init(unsigned argc, char *argv[]);
void options_destroy(options_s *options);
int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *stream, server_s *server);

375
src/ustreamer/stream.c Normal file
View File

@@ -0,0 +1,375 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 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 "stream.h"
static workers_pool_s *_stream_init_loop(stream_s *stream);
static workers_pool_s *_stream_init_one(stream_s *stream);
static bool _stream_expose_frame(stream_s *stream, frame_s *frame, unsigned captured_fps);
#define RUN(_next) stream->run->_next
stream_s *stream_init(device_s *dev, encoder_s *enc) {
stream_runtime_s *run;
A_CALLOC(run, 1);
atomic_init(&run->stop, false);
video_s *video;
A_CALLOC(video, 1);
video->frame = frame_init("stream_video");
atomic_init(&video->updated, false);
A_MUTEX_INIT(&video->mutex);
atomic_init(&video->has_clients, false);
run->video = video;
stream_s *stream;
A_CALLOC(stream, 1);
stream->dev = dev;
stream->enc = enc;
stream->last_as_blank = -1;
stream->error_delay = 1;
# ifdef WITH_OMX
stream->h264_bitrate = 5000; // Kbps
stream->h264_gop = 30;
# endif
stream->run = run;
return stream;
}
void stream_destroy(stream_s *stream) {
A_MUTEX_DESTROY(&RUN(video->mutex));
frame_destroy(RUN(video->frame));
free(RUN(video));
free(stream->run);
free(stream);
}
void stream_loop(stream_s *stream) {
assert(stream->blank);
LOG_INFO("Using V4L2 device: %s", stream->dev->path);
LOG_INFO("Using desired FPS: %u", stream->dev->desired_fps);
# ifdef WITH_OMX
if (stream->h264_sink) {
RUN(h264) = h264_stream_init(stream->h264_sink, stream->h264_bitrate, stream->h264_gop);
}
# endif
for (workers_pool_s *pool; (pool = _stream_init_loop(stream)) != NULL;) {
long double grab_after = 0;
unsigned fluency_passed = 0;
unsigned captured_fps = 0;
unsigned captured_fps_accum = 0;
long long captured_fps_second = 0;
LOG_INFO("Capturing ...");
while (!atomic_load(&RUN(stop))) {
SEP_DEBUG('-');
LOG_DEBUG("Waiting for worker ...");
worker_s *ready_wr = workers_pool_wait(pool);
encoder_job_s *ready_job = (encoder_job_s *)(ready_wr->job);
if (ready_job->hw) {
if (device_release_buffer(stream->dev, ready_job->hw) < 0) {
ready_wr->job_failed = true;
}
ready_job->hw = NULL;
if (!ready_wr->job_failed) {
if (ready_wr->job_timely) {
_stream_expose_frame(stream, ready_job->dest, captured_fps);
LOG_PERF("##### Encoded frame exposed; worker=%s", ready_wr->name);
} else {
LOG_PERF("----- Encoded frame dropped; worker=%s", ready_wr->name);
}
} else {
break;
}
}
# ifdef WITH_OMX
bool h264_force_key = false;
# endif
if (stream->slowdown) {
unsigned slc = 0;
while (
slc < 10
&& !atomic_load(&RUN(stop))
&& !atomic_load(&RUN(video->has_clients))
// has_clients синков НЕ обновляются в реальном времени
&& (stream->sink == NULL || !stream->sink->has_clients)
# ifdef WITH_OMX
&& (RUN(h264) == NULL || /*RUN(h264->sink) == NULL ||*/ !RUN(h264->sink->has_clients))
# endif
) {
usleep(100000);
++slc;
}
# ifdef WITH_OMX
h264_force_key = (slc == 10);
# endif
}
if (atomic_load(&RUN(stop))) {
break;
}
bool has_read;
bool has_write;
bool has_error;
int selected = device_select(stream->dev, &has_read, &has_write, &has_error);
if (selected < 0) {
if (errno != EINTR) {
LOG_PERROR("Mainloop select() error");
break;
}
} else if (selected == 0) { // Persistent timeout
# ifdef WITH_GPIO
gpio_set_stream_online(false);
# endif
} else {
if (has_read) {
LOG_DEBUG("Frame is ready");
# ifdef WITH_GPIO
gpio_set_stream_online(true);
# endif
const long double now = get_now_monotonic();
const long long now_second = floor_ms(now);
hw_buffer_s *hw;
int buf_index = device_grab_buffer(stream->dev, &hw);
if (buf_index >= 0) {
if (now < grab_after) {
fluency_passed += 1;
LOG_VERBOSE("Passed %u frames for fluency: now=%.03Lf, grab_after=%.03Lf",
fluency_passed, now, grab_after);
if (device_release_buffer(stream->dev, hw) < 0) {
break;
}
} else {
fluency_passed = 0;
if (now_second != captured_fps_second) {
captured_fps = captured_fps_accum;
captured_fps_accum = 0;
captured_fps_second = now_second;
LOG_PERF_FPS("A new second has come; captured_fps=%u", captured_fps);
}
captured_fps_accum += 1;
const long double fluency_delay = workers_pool_get_fluency_delay(pool, ready_wr);
grab_after = now + fluency_delay;
LOG_VERBOSE("Fluency: delay=%.03Lf, grab_after=%.03Lf", fluency_delay, grab_after);
ready_job->hw = hw;
workers_pool_assign(pool, ready_wr);
LOG_DEBUG("Assigned new frame in buffer %d to worker %s", buf_index, ready_wr->name);
if (stream->raw_sink) {
if (memsink_server_check(stream->raw_sink, &hw->raw)) {
memsink_server_put(stream->raw_sink, &hw->raw);
}
}
# ifdef WITH_OMX
if (RUN(h264)) {
h264_stream_process(RUN(h264), &hw->raw, hw->vcsm_handle, h264_force_key);
}
# endif
}
} else if (buf_index != -2) { // -2 for broken frame
break;
}
}
if (has_write) {
LOG_ERROR("Got unexpected writing event, seems device was disconnected");
break;
}
if (has_error) {
LOG_INFO("Got V4L2 event");
if (device_consume_event(stream->dev) < 0) {
break;
}
}
}
}
workers_pool_destroy(pool);
device_switch_capturing(stream->dev, false);
device_close(stream->dev);
# ifdef WITH_GPIO
gpio_set_stream_online(false);
# endif
}
# ifdef WITH_OMX
if (RUN(h264)) {
h264_stream_destroy(RUN(h264));
}
# endif
}
void stream_loop_break(stream_s *stream) {
atomic_store(&RUN(stop), true);
}
static workers_pool_s *_stream_init_loop(stream_s *stream) {
workers_pool_s *pool = NULL;
int access_error = 0;
LOG_DEBUG("%s: stream->run->stop=%d", __FUNCTION__, atomic_load(&RUN(stop)));
while (!atomic_load(&RUN(stop))) {
if (_stream_expose_frame(stream, NULL, 0)) {
if (stream->raw_sink) {
if (memsink_server_check(stream->raw_sink, stream->blank)) {
memsink_server_put(stream->raw_sink, stream->blank);
}
}
# ifdef WITH_OMX
if (RUN(h264)) {
h264_stream_process(RUN(h264), stream->blank, -1, false);
}
# endif
}
if (access(stream->dev->path, R_OK|W_OK) < 0) {
if (access_error != errno) {
SEP_INFO('=');
LOG_PERROR("Can't access device");
LOG_INFO("Waiting for the device access ...");
access_error = errno;
}
sleep(stream->error_delay);
continue;
} else {
SEP_INFO('=');
access_error = 0;
}
if ((pool = _stream_init_one(stream)) == NULL) {
LOG_INFO("Sleeping %u seconds before new stream init ...", stream->error_delay);
sleep(stream->error_delay);
} else {
break;
}
}
return pool;
}
static workers_pool_s *_stream_init_one(stream_s *stream) {
if (device_open(stream->dev) < 0) {
goto error;
}
# ifdef WITH_OMX
if (RUN(h264) && !is_jpeg(stream->dev->run->format)) {
device_export_to_vcsm(stream->dev);
}
# endif
if (device_switch_capturing(stream->dev, true) < 0) {
goto error;
}
return encoder_workers_pool_init(stream->enc, stream->dev);
error:
device_close(stream->dev);
return NULL;
}
static bool _stream_expose_frame(stream_s *stream, frame_s *frame, unsigned captured_fps) {
# define VID(_next) RUN(video->_next)
frame_s *new = NULL;
bool changed = false;
A_MUTEX_LOCK(&VID(mutex));
if (frame) {
new = frame;
RUN(last_as_blank_ts) = 0; // Останавливаем таймер
LOG_DEBUG("Exposed ALIVE video frame");
} else {
if (VID(frame->online)) { // Если переходим из online в offline
if (stream->last_as_blank < 0) { // Если last_as_blank выключен, просто покажем старую картинку
new = stream->blank;
LOG_INFO("Changed video frame to BLANK");
} else if (stream->last_as_blank > 0) { // // Если нужен таймер - запустим
RUN(last_as_blank_ts) = get_now_monotonic() + stream->last_as_blank;
LOG_INFO("Freezed last ALIVE video frame for %d seconds", stream->last_as_blank);
} else { // last_as_blank == 0 - показываем последний фрейм вечно
LOG_INFO("Freezed last ALIVE video frame forever");
}
} else if (stream->last_as_blank < 0) {
new = stream->blank;
// LOG_INFO("Changed video frame to BLANK");
}
if ( // Если уже оффлайн, включена фича last_as_blank с таймером и он запущен
stream->last_as_blank > 0
&& RUN(last_as_blank_ts) != 0
&& RUN(last_as_blank_ts) < get_now_monotonic()
) {
new = stream->blank;
RUN(last_as_blank_ts) = 0; // // Останавливаем таймер
LOG_INFO("Changed last ALIVE video frame to BLANK");
}
}
if (new) {
frame_copy(new, VID(frame));
changed = true;
} else if (VID(frame->used) == 0) { // Инициализация
frame_copy(stream->blank, VID(frame));
frame = NULL;
changed = true;
}
VID(frame->online) = frame;
VID(captured_fps) = captured_fps;
atomic_store(&VID(updated), true);
A_MUTEX_UNLOCK(&VID(mutex));
if (changed && stream->sink) {
if (memsink_server_check(stream->sink, VID(frame))) {
memsink_server_put(stream->sink, VID(frame));
}
}
return changed;
# undef VID
}
#undef RUN

99
src/ustreamer/stream.h Normal file
View File

@@ -0,0 +1,99 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 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 <stdatomic.h>
#include <unistd.h>
#include <errno.h>
#include <assert.h>
#include <pthread.h>
#include <linux/videodev2.h>
#include "../libs/tools.h"
#include "../libs/threading.h"
#include "../libs/logging.h"
#include "../libs/frame.h"
#include "../libs/memsink.h"
#include "blank.h"
#include "device.h"
#include "encoder.h"
#include "workers.h"
#ifdef WITH_OMX
# include "h264/stream.h"
#endif
#ifdef WITH_GPIO
# include "gpio/gpio.h"
#endif
typedef struct {
frame_s *frame;
unsigned captured_fps;
atomic_bool updated;
pthread_mutex_t mutex;
atomic_bool has_clients; // For slowdown
} video_s;
typedef struct {
video_s *video;
long double last_as_blank_ts;
# ifdef WITH_OMX
h264_stream_s *h264;
# endif
atomic_bool stop;
} stream_runtime_s;
typedef struct {
device_s *dev;
encoder_s *enc;
frame_s *blank;
int last_as_blank;
bool slowdown;
unsigned error_delay;
memsink_s *sink;
memsink_s *raw_sink;
# ifdef WITH_OMX
memsink_s *h264_sink;
unsigned h264_bitrate;
unsigned h264_gop;
# endif
stream_runtime_s *run;
} stream_s;
stream_s *stream_init(device_s *dev, encoder_s *enc);
void stream_destroy(stream_s *stream);
void stream_loop(stream_s *stream);
void stream_loop_break(stream_s *stream);

213
src/ustreamer/workers.c Normal file
View File

@@ -0,0 +1,213 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 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 "workers.h"
static void *_worker_thread(void *v_worker);
workers_pool_s *workers_pool_init(
const char *name, const char *wr_prefix, unsigned n_workers, long double desired_interval,
void *(*job_init)(worker_s *wr, void *arg), void *job_init_arg,
void (*job_destroy)(void *),
bool (*run_job)(worker_s *)) {
LOG_INFO("Creating pool %s with %u workers ...", name, n_workers);
workers_pool_s *pool;
A_CALLOC(pool, 1);
pool->name = name;
pool->desired_interval = desired_interval;
pool->job_destroy = job_destroy;
pool->run_job = run_job;
atomic_init(&pool->stop, false);
pool->n_workers = n_workers;
A_CALLOC(pool->workers, pool->n_workers);
A_MUTEX_INIT(&pool->free_workers_mutex);
A_COND_INIT(&pool->free_workers_cond);
const size_t wr_name_len = strlen(wr_prefix) + 64;
for (unsigned number = 0; number < pool->n_workers; ++number) {
# define WR(_next) pool->workers[number]._next
WR(number) = number;
A_CALLOC(WR(name), wr_name_len);
snprintf(WR(name), wr_name_len, "%s-%u", wr_prefix, number);
A_MUTEX_INIT(&WR(has_job_mutex));
atomic_init(&WR(has_job), false);
A_COND_INIT(&WR(has_job_cond));
WR(pool) = pool;
WR(job) = job_init(&pool->workers[number], job_init_arg);
A_THREAD_CREATE(&WR(tid), _worker_thread, (void *)&(pool->workers[number]));
pool->free_workers += 1;
# undef WR
}
return pool;
}
void workers_pool_destroy(workers_pool_s *pool) {
LOG_INFO("Destroying workers pool %s ...", pool->name);
atomic_store(&pool->stop, true);
for (unsigned number = 0; number < pool->n_workers; ++number) {
# define WR(_next) pool->workers[number]._next
A_MUTEX_LOCK(&WR(has_job_mutex));
atomic_store(&WR(has_job), true); // Final job: die
A_MUTEX_UNLOCK(&WR(has_job_mutex));
A_COND_SIGNAL(&WR(has_job_cond));
A_THREAD_JOIN(WR(tid));
A_MUTEX_DESTROY(&WR(has_job_mutex));
A_COND_DESTROY(&WR(has_job_cond));
free(WR(name));
pool->job_destroy(WR(job));
# undef WR
}
A_MUTEX_DESTROY(&pool->free_workers_mutex);
A_COND_DESTROY(&pool->free_workers_cond);
free(pool->workers);
free(pool);
}
worker_s *workers_pool_wait(workers_pool_s *pool) {
worker_s *ready_wr = NULL;
A_MUTEX_LOCK(&pool->free_workers_mutex);
A_COND_WAIT_TRUE(pool->free_workers, &pool->free_workers_cond, &pool->free_workers_mutex);
A_MUTEX_UNLOCK(&pool->free_workers_mutex);
if (pool->oldest_wr && !atomic_load(&pool->oldest_wr->has_job)) {
ready_wr = pool->oldest_wr;
ready_wr->job_timely = true;
pool->oldest_wr = pool->oldest_wr->next_wr;
} else {
for (unsigned number = 0; number < pool->n_workers; ++number) {
if (
!atomic_load(&pool->workers[number].has_job) && (
ready_wr == NULL
|| ready_wr->job_start_ts < pool->workers[number].job_start_ts
)
) {
ready_wr = &pool->workers[number];
break;
}
}
assert(ready_wr != NULL);
ready_wr->job_timely = false; // Освободился воркер, получивший задание позже (или самый первый при самом первом захвате)
}
return ready_wr;
}
void workers_pool_assign(workers_pool_s *pool, worker_s *ready_wr/*, void *job*/) {
if (pool->oldest_wr == NULL) {
pool->oldest_wr = ready_wr;
pool->latest_wr = pool->oldest_wr;
} else {
if (ready_wr->next_wr) {
ready_wr->next_wr->prev_wr = ready_wr->prev_wr;
}
if (ready_wr->prev_wr) {
ready_wr->prev_wr->next_wr = ready_wr->next_wr;
}
ready_wr->prev_wr = pool->latest_wr;
pool->latest_wr->next_wr = ready_wr;
pool->latest_wr = ready_wr;
}
pool->latest_wr->next_wr = NULL;
A_MUTEX_LOCK(&ready_wr->has_job_mutex);
//ready_wr->job = job;
atomic_store(&ready_wr->has_job, true);
A_MUTEX_UNLOCK(&ready_wr->has_job_mutex);
A_COND_SIGNAL(&ready_wr->has_job_cond);
A_MUTEX_LOCK(&pool->free_workers_mutex);
pool->free_workers -= 1;
A_MUTEX_UNLOCK(&pool->free_workers_mutex);
}
long double workers_pool_get_fluency_delay(workers_pool_s *pool, worker_s *ready_wr) {
const long double approx_job_time = pool->approx_job_time * 0.9 + ready_wr->last_job_time * 0.1;
LOG_VERBOSE("Correcting pool's %s approx_job_time: %.3Lf -> %.3Lf (last_job_time=%.3Lf)",
pool->name, pool->approx_job_time, approx_job_time, ready_wr->last_job_time);
pool->approx_job_time = approx_job_time;
const long double min_delay = pool->approx_job_time / pool->n_workers; // Среднее время работы размазывается на N воркеров
if (pool->desired_interval > 0 && min_delay > 0 && pool->desired_interval > min_delay) {
// Искусственное время задержки на основе желаемого FPS, если включен --desired-fps
// и аппаратный fps не попадает точно в желаемое значение
return pool->desired_interval;
}
return min_delay;
}
static void *_worker_thread(void *v_worker) {
worker_s *wr = (worker_s *)v_worker;
A_THREAD_RENAME("%s", wr->name);
LOG_DEBUG("Hello! I am a worker %s ^_^", wr->name);
while (!atomic_load(&wr->pool->stop)) {
LOG_DEBUG("Worker %s waiting for a new job ...", wr->name);
A_MUTEX_LOCK(&wr->has_job_mutex);
A_COND_WAIT_TRUE(atomic_load(&wr->has_job), &wr->has_job_cond, &wr->has_job_mutex);
A_MUTEX_UNLOCK(&wr->has_job_mutex);
if (!atomic_load(&wr->pool->stop)) {
long double job_start_ts = get_now_monotonic();
wr->job_failed = !wr->pool->run_job(wr);
if (!wr->job_failed) {
wr->job_start_ts = job_start_ts;
wr->last_job_time = get_now_monotonic() - wr->job_start_ts;
}
//wr->job = NULL;
atomic_store(&wr->has_job, false);
}
A_MUTEX_LOCK(&wr->pool->free_workers_mutex);
wr->pool->free_workers += 1;
A_MUTEX_UNLOCK(&wr->pool->free_workers_mutex);
A_COND_SIGNAL(&wr->pool->free_workers_cond);
}
LOG_DEBUG("Bye-bye (worker %s)", wr->name);
return NULL;
}

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

@@ -0,0 +1,91 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <stdbool.h>
#include <stdatomic.h>
#include <sys/types.h>
#include <pthread.h>
#include "../libs/tools.h"
#include "../libs/threading.h"
#include "../libs/logging.h"
typedef struct worker_sx {
pthread_t tid;
unsigned number;
char *name;
long double last_job_time;
pthread_mutex_t has_job_mutex;
void *job;
atomic_bool has_job;
bool job_timely;
bool job_failed;
long double job_start_ts;
pthread_cond_t has_job_cond;
struct worker_sx *prev_wr;
struct worker_sx *next_wr;
struct workers_pool_sx *pool;
} worker_s;
typedef struct workers_pool_sx {
const char *name;
long double desired_interval;
bool (*run_job)(worker_s *wr);
void (*job_destroy)(void *job);
unsigned n_workers;
worker_s *workers;
worker_s *oldest_wr;
worker_s *latest_wr;
long double approx_job_time;
pthread_mutex_t free_workers_mutex;
unsigned free_workers;
pthread_cond_t free_workers_cond;
atomic_bool stop;
} workers_pool_s;
workers_pool_s *workers_pool_init(
const char *name, const char *wr_prefix, unsigned n_workers, long double desired_interval,
void *(*job_init)(worker_s *wr, void *arg), void *job_init_arg,
void (*job_destroy)(void *job),
bool (*run_job)(worker_s *));
void workers_pool_destroy(workers_pool_s *pool);
worker_s *workers_pool_wait(workers_pool_s *pool);
void workers_pool_assign(workers_pool_s *pool, worker_s *ready_wr/*, void *job*/);
long double workers_pool_get_fluency_delay(workers_pool_s *pool, worker_s *ready_wr);

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 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,8 +26,8 @@
#include <sys/ioctl.h>
#include "tools.h"
#include "logging.h"
#include "../libs/tools.h"
#include "../libs/logging.h"
#ifndef CFG_XIOCTL_RETRIES

View File

@@ -3,7 +3,7 @@
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
@@ -30,7 +30,7 @@ C_PREPEND = textwrap.dedent("""
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2018-2021 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 #
@@ -48,5 +48,4 @@ C_PREPEND = textwrap.dedent("""
*****************************************************************************/
#pragma once
""").strip() + "\n"

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