mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-19 08:16:31 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
35ed415f4c | ||
|
|
121edf5a10 | ||
|
|
aa90ed1fbb | ||
|
|
a102a4a3db | ||
|
|
516c0be6ea | ||
|
|
0745f0a75a | ||
|
|
90e51c0619 | ||
|
|
cb9c1658af | ||
|
|
548c261d92 | ||
|
|
d4560fcba9 | ||
|
|
370434601c |
@@ -1,7 +1,7 @@
|
||||
[bumpversion]
|
||||
commit = True
|
||||
tag = True
|
||||
current_version = 5.40
|
||||
current_version = 5.42
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)
|
||||
serialize =
|
||||
{major}.{minor}
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,6 +4,7 @@
|
||||
/pkg/arch/src/
|
||||
/src/build/
|
||||
/python/build/
|
||||
/python/ustreamer.egg-info/
|
||||
/janus/build/
|
||||
/ustreamer
|
||||
/ustreamer-dump
|
||||
|
||||
19
README.md
19
README.md
@@ -88,7 +88,7 @@ Without arguments, ```ustreamer``` will try to open ```/dev/video0``` with 640x4
|
||||
:exclamation: Please note that since µStreamer v2.0 cross-domain requests were disabled by default for [security reasons](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). To enable the old behavior, use the option `--allow-origin=\*`.
|
||||
|
||||
The recommended way of running µStreamer with [Auvidea B101](https://www.raspberrypi.org/forums/viewtopic.php?f=38&t=120702&start=400#p1339178) on Raspberry Pi:
|
||||
```bash
|
||||
```
|
||||
$ ./ustreamer \
|
||||
--format=uyvy \ # Device input format
|
||||
--encoder=m2m-image \ # Hardware encoding on V4L2 M2M driver
|
||||
@@ -115,7 +115,7 @@ dtoverlay=tc358743
|
||||
|
||||
Check size of CMA:
|
||||
|
||||
```bash
|
||||
```
|
||||
$ dmesg | grep cma-reserved
|
||||
[ 0.000000] Memory: 7700524K/8244224K available (11772K kernel code, 1278K rwdata, 4320K rodata, 4096K init, 1077K bss, 281556K reserved, 262144K cma-reserved)
|
||||
```
|
||||
@@ -131,14 +131,14 @@ Save changes and reboot.
|
||||
## Launch
|
||||
Start container:
|
||||
|
||||
```bash
|
||||
```
|
||||
$ docker run --device /dev/video0:/dev/video0 -e EDID=1 -p 8080:8080 pikvm/ustreamer:latest
|
||||
```
|
||||
|
||||
Then access the web interface at port 8080 (e.g. http://raspberrypi.local:8080).
|
||||
|
||||
## Custom config
|
||||
```bash
|
||||
```
|
||||
$ docker run --rm pikvm/ustreamer:latest \
|
||||
--format=uyvy \
|
||||
--workers=3 \
|
||||
@@ -153,8 +153,15 @@ Add `-e EDID=1` to set HDMI EDID before starting ustreamer. Use together with `-
|
||||
-----
|
||||
# Raspberry Pi Camera Example
|
||||
|
||||
Example usage for the Raspberry Pi v3 camera (required `libcamerify` which is located in `libcamera-tools` on Raspbian):
|
||||
```
|
||||
$ sudo modprobe bcm2835-v4l2
|
||||
$ libcamerify ./ustreamer --host :: -e m2m-image
|
||||
```
|
||||
|
||||
|
||||
Example usage for the Raspberry Pi v1 camera:
|
||||
```bash
|
||||
```
|
||||
$ sudo modprobe bcm2835-v4l2
|
||||
$ ./ustreamer --host :: -m jpeg --device-timeout=5 --buffers=3 -r 2592x1944
|
||||
```
|
||||
@@ -163,7 +170,7 @@ $ ./ustreamer --host :: -m jpeg --device-timeout=5 --buffers=3 -r 2592x1944
|
||||
|
||||
:exclamation: If you get a poor framerate, it could be that the camera is switched to photo mode, which produces a low framerate (but a higher quality picture). This is because `bcm2835-v4l2` switches to photo mode at resolutions higher than `1280x720`. To work around this, pass the `max_video_width` and `max_video_height` module parameters like so:
|
||||
|
||||
```bash
|
||||
```
|
||||
$ modprobe bcm2835-v4l2 max_video_width=2592 max_video_height=1944
|
||||
```
|
||||
|
||||
|
||||
@@ -116,7 +116,9 @@ static void *_common_thread(void *v_client, bool video) {
|
||||
packet.extensions.max_delay = 0;
|
||||
} else {
|
||||
packet.extensions.min_delay = 0;
|
||||
packet.extensions.max_delay = 1000;
|
||||
// 10s - Chromium/WebRTC default
|
||||
// 3s - Firefox default
|
||||
packet.extensions.max_delay = 300; // == 3s, i.e. 10ms granularity
|
||||
}*/
|
||||
|
||||
client->gw->relay_rtp(client->session, &packet);
|
||||
|
||||
@@ -56,8 +56,8 @@
|
||||
#include "config.h"
|
||||
|
||||
|
||||
static us_config_s *_g_config = NULL;
|
||||
const useconds_t _g_watchers_polling = 100000;
|
||||
static us_config_s *_g_config = NULL;
|
||||
static const useconds_t _g_watchers_polling = 100000;
|
||||
|
||||
static us_janus_client_s *_g_clients = NULL;
|
||||
static janus_callbacks *_g_gw = NULL;
|
||||
@@ -107,7 +107,8 @@ static void *_video_rtp_thread(UNUSED void *arg) {
|
||||
us_frame_s *frame;
|
||||
if (us_queue_get(_g_video_queue, (void **)&frame, 0.1) == 0) {
|
||||
_LOCK_VIDEO;
|
||||
us_rtpv_wrap(_g_rtpv, frame);
|
||||
const bool zero_playout_delay = (frame->gop == 0);
|
||||
us_rtpv_wrap(_g_rtpv, frame, zero_playout_delay);
|
||||
_UNLOCK_VIDEO;
|
||||
us_frame_destroy(frame);
|
||||
}
|
||||
|
||||
@@ -71,13 +71,13 @@ char *us_rtpv_make_sdp(us_rtpv_s *rtpv) {
|
||||
|
||||
#define _PRE 3 // Annex B prefix length
|
||||
|
||||
void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame) {
|
||||
void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame, bool zero_playout_delay) {
|
||||
// There is a complicated logic here but everything works as it should:
|
||||
// - https://github.com/pikvm/ustreamer/issues/115#issuecomment-893071775
|
||||
|
||||
assert(frame->format == V4L2_PIX_FMT_H264);
|
||||
|
||||
rtpv->rtp->zero_playout_delay = (frame->gop == 0);
|
||||
rtpv->rtp->zero_playout_delay = zero_playout_delay;
|
||||
|
||||
const uint32_t pts = us_get_now_monotonic_u64() * 9 / 100; // PTS units are in 90 kHz
|
||||
ssize_t last_offset = -_PRE;
|
||||
|
||||
@@ -47,4 +47,4 @@ us_rtpv_s *us_rtpv_init(us_rtp_callback_f callback);
|
||||
void us_rtpv_destroy(us_rtpv_s *rtpv);
|
||||
|
||||
char *us_rtpv_make_sdp(us_rtpv_s *rtpv);
|
||||
void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame);
|
||||
void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame, bool zero_playout_delay);
|
||||
|
||||
@@ -2,5 +2,3 @@
|
||||
#define WITH_GPIO
|
||||
#define JANUS_PLUGIN_INIT(...) { __VA_ARGS__ }
|
||||
#define EVTHREAD_USE_PTHREADS_IMPLEMENTED 1
|
||||
#define CLOCK_MONOTONIC_RAW 1
|
||||
#define CLOCK_MONOTONIC_FAST 1
|
||||
|
||||
@@ -9,7 +9,6 @@ changedir = /src
|
||||
[testenv:cppcheck]
|
||||
allowlist_externals = cppcheck
|
||||
commands = cppcheck \
|
||||
-j4 \
|
||||
--force \
|
||||
--std=c17 \
|
||||
--error-exitcode=1 \
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Manpage for ustreamer-dump.
|
||||
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
|
||||
.TH USTREAMER-DUMP 1 "version 5.40" "January 2021"
|
||||
.TH USTREAMER-DUMP 1 "version 5.42" "January 2021"
|
||||
|
||||
.SH NAME
|
||||
ustreamer-dump \- Dump uStreamer's memory sink to file
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Manpage for ustreamer.
|
||||
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
|
||||
.TH USTREAMER 1 "version 5.40" "November 2020"
|
||||
.TH USTREAMER 1 "version 5.42" "November 2020"
|
||||
|
||||
.SH NAME
|
||||
ustreamer \- stream MJPEG video from any V4L2 device to the network
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
pkgname=ustreamer
|
||||
pkgver=5.40
|
||||
pkgver=5.42
|
||||
pkgrel=1
|
||||
pkgdesc="Lightweight and fast MJPEG-HTTP streamer"
|
||||
url="https://github.com/pikvm/ustreamer"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=ustreamer
|
||||
PKG_VERSION:=5.40
|
||||
PKG_VERSION:=5.42
|
||||
PKG_RELEASE:=1
|
||||
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
||||
|
||||
|
||||
@@ -17,4 +17,4 @@ install:
|
||||
|
||||
|
||||
clean:
|
||||
rm -rf build
|
||||
rm -rf build ustreamer.egg-info
|
||||
|
||||
@@ -17,7 +17,7 @@ def _find_sources(suffix: str) -> list[str]:
|
||||
if __name__ == "__main__":
|
||||
setup(
|
||||
name="ustreamer",
|
||||
version="5.40",
|
||||
version="5.42",
|
||||
description="uStreamer tools",
|
||||
author="Maxim Devaev",
|
||||
author_email="mdevaev@gmail.com",
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#pragma once
|
||||
|
||||
#define US_VERSION_MAJOR 5
|
||||
#define US_VERSION_MINOR 40
|
||||
#define US_VERSION_MINOR 42
|
||||
|
||||
#define US_MAKE_VERSION2(_major, _minor) #_major "." #_minor
|
||||
#define US_MAKE_VERSION1(_major, _minor) US_MAKE_VERSION2(_major, _minor)
|
||||
|
||||
@@ -98,7 +98,7 @@ INLINE int us_process_track_parent_death(void) {
|
||||
#ifdef WITH_SETPROCTITLE
|
||||
# pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
# pragma GCC diagnostic push
|
||||
INLINE void us_process_set_name_prefix(int argc, char *argv[], const char *prefix) {
|
||||
INLINE void us_process_set_name_prefix(int argc, char *argv[], const char *prefix) { // cppcheck-suppress constParameter
|
||||
# pragma GCC diagnostic pop
|
||||
|
||||
char *cmdline = NULL;
|
||||
|
||||
@@ -117,29 +117,19 @@ INLINE void us_get_now(clockid_t clk_id, time_t *sec, long *msec) {
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(CLOCK_MONOTONIC_RAW)
|
||||
# define _X_CLOCK_MONOTONIC CLOCK_MONOTONIC_RAW
|
||||
#elif defined(CLOCK_MONOTONIC_FAST)
|
||||
# define _X_CLOCK_MONOTONIC CLOCK_MONOTONIC_FAST
|
||||
#else
|
||||
# define _X_CLOCK_MONOTONIC CLOCK_MONOTONIC
|
||||
#endif
|
||||
|
||||
INLINE long double us_get_now_monotonic(void) {
|
||||
time_t sec;
|
||||
long msec;
|
||||
us_get_now(_X_CLOCK_MONOTONIC, &sec, &msec);
|
||||
us_get_now(CLOCK_MONOTONIC, &sec, &msec);
|
||||
return (long double)sec + ((long double)msec) / 1000;
|
||||
}
|
||||
|
||||
INLINE uint64_t us_get_now_monotonic_u64(void) {
|
||||
struct timespec ts;
|
||||
assert(!clock_gettime(_X_CLOCK_MONOTONIC, &ts));
|
||||
assert(!clock_gettime(CLOCK_MONOTONIC, &ts));
|
||||
return (uint64_t)(ts.tv_nsec / 1000) + (uint64_t)ts.tv_sec * 1000000;
|
||||
}
|
||||
|
||||
#undef _X_CLOCK_MONOTONIC
|
||||
|
||||
INLINE uint64_t us_get_now_id(void) {
|
||||
const uint64_t now = us_get_now_monotonic_u64();
|
||||
return (uint64_t)us_triple_u32(now) | ((uint64_t)us_triple_u32(now + 12345) << 32);
|
||||
|
||||
@@ -71,7 +71,7 @@ static int _device_query_control(
|
||||
us_device_s *dev, struct v4l2_queryctrl *query,
|
||||
const char *name, unsigned cid, bool quiet);
|
||||
static void _device_set_control(
|
||||
us_device_s *dev, struct v4l2_queryctrl *query,
|
||||
us_device_s *dev, const struct v4l2_queryctrl *query,
|
||||
const char *name, unsigned cid, int value, bool quiet);
|
||||
|
||||
static const char *_format_to_string_nullable(unsigned format);
|
||||
@@ -310,16 +310,37 @@ int us_device_grab_buffer(us_device_s *dev, us_hw_buffer_s **hw) {
|
||||
*hw = NULL;
|
||||
|
||||
struct v4l2_buffer buf = {0};
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = dev->io_method;
|
||||
bool buf_got = false;
|
||||
unsigned skipped = 0;
|
||||
|
||||
US_LOG_DEBUG("Grabbing device buffer ...");
|
||||
if (_D_XIOCTL(VIDIOC_DQBUF, &buf) < 0) {
|
||||
US_LOG_PERROR("Can't grab device buffer");
|
||||
return -1;
|
||||
}
|
||||
|
||||
US_LOG_DEBUG("Grabbed new frame: buffer=%u, bytesused=%u", buf.index, buf.bytesused);
|
||||
do {
|
||||
struct v4l2_buffer new = {0};
|
||||
new.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
new.memory = dev->io_method;
|
||||
const bool new_got = (_D_XIOCTL(VIDIOC_DQBUF, &new) >= 0);
|
||||
|
||||
if (new_got) {
|
||||
if (buf_got) {
|
||||
if (_D_XIOCTL(VIDIOC_QBUF, &buf) < 0) {
|
||||
US_LOG_PERROR("Can't release device buffer=%u (skipped frame)", buf.index);
|
||||
return -1;
|
||||
}
|
||||
++skipped;
|
||||
// buf_got = false;
|
||||
}
|
||||
memcpy(&buf, &new, sizeof(struct v4l2_buffer));
|
||||
buf_got = true;
|
||||
} else {
|
||||
if (buf_got && errno == EAGAIN) {
|
||||
break;
|
||||
} else {
|
||||
US_LOG_PERROR("Can't grab device buffer");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
} while (true);
|
||||
|
||||
if (buf.index >= _RUN(n_bufs)) {
|
||||
US_LOG_ERROR("V4L2 error: grabbed invalid device buffer=%u, n_bufs=%u", buf.index, _RUN(n_bufs));
|
||||
@@ -358,7 +379,9 @@ int us_device_grab_buffer(us_device_s *dev, us_hw_buffer_s **hw) {
|
||||
HW(raw.stride) = _RUN(stride);
|
||||
HW(raw.online) = true;
|
||||
memcpy(&HW(buf), &buf, sizeof(struct v4l2_buffer));
|
||||
HW(raw.grab_ts) = us_get_now_monotonic();
|
||||
HW(raw.grab_ts)= (long double)((buf.timestamp.tv_sec * (uint64_t)1000) + (buf.timestamp.tv_usec / 1000)) / 1000;
|
||||
US_LOG_DEBUG("Grabbed new frame: buffer=%u, bytesused=%u, grab_ts=%.3Lf, latency=%.3Lf, skipped=%u",
|
||||
buf.index, buf.bytesused, HW(raw.grab_ts), us_get_now_monotonic() - HW(raw.grab_ts), skipped);
|
||||
|
||||
# undef HW
|
||||
*hw = &_RUN(hw_bufs[buf.index]);
|
||||
@@ -843,7 +866,7 @@ static int _device_query_control(
|
||||
}
|
||||
|
||||
static void _device_set_control(
|
||||
us_device_s *dev, struct v4l2_queryctrl *query,
|
||||
us_device_s *dev, const struct v4l2_queryctrl *query,
|
||||
const char *name, unsigned cid, int value, bool quiet) {
|
||||
|
||||
if (value < query->minimum || value > query->maximum || value % query->step != 0) {
|
||||
|
||||
@@ -50,10 +50,10 @@
|
||||
|
||||
|
||||
#define US_VIDEO_MIN_WIDTH ((unsigned)160)
|
||||
#define US_VIDEO_MAX_WIDTH ((unsigned)10240)
|
||||
#define US_VIDEO_MAX_WIDTH ((unsigned)15360)
|
||||
|
||||
#define US_VIDEO_MIN_HEIGHT ((unsigned)120)
|
||||
#define US_VIDEO_MAX_HEIGHT ((unsigned)4320)
|
||||
#define US_VIDEO_MAX_HEIGHT ((unsigned)8640)
|
||||
|
||||
#define US_VIDEO_MAX_FPS ((unsigned)120)
|
||||
|
||||
|
||||
@@ -195,7 +195,7 @@ static void _worker_job_destroy(void *v_job) {
|
||||
static bool _worker_run_job(us_worker_s *wr) {
|
||||
us_encoder_job_s *job = (us_encoder_job_s *)wr->job;
|
||||
us_encoder_s *enc = job->enc; // Just for _ER()
|
||||
us_frame_s *src = &job->hw->raw;
|
||||
const us_frame_s *src = &job->hw->raw;
|
||||
us_frame_s *dest = job->dest;
|
||||
|
||||
assert(_ER(type) != US_ENCODER_TYPE_UNKNOWN);
|
||||
|
||||
@@ -233,7 +233,7 @@ static int _parse_resolution(const char *str, unsigned *width, unsigned *height,
|
||||
static int _check_instance_id(const char *str);
|
||||
|
||||
static void _features(void);
|
||||
static void _help(FILE *fp, us_device_s *dev, us_encoder_s *enc, us_stream_s *stream, us_server_s *server);
|
||||
static void _help(FILE *fp, const us_device_s *dev, const us_encoder_s *enc, const us_stream_s *stream, const us_server_s *server);
|
||||
|
||||
|
||||
us_options_s *us_options_init(unsigned argc, char *argv[]) {
|
||||
@@ -586,7 +586,7 @@ static void _features(void) {
|
||||
# endif
|
||||
}
|
||||
|
||||
static void _help(FILE *fp, us_device_s *dev, us_encoder_s *enc, us_stream_s *stream, us_server_s *server) {
|
||||
static void _help(FILE *fp, const us_device_s *dev, const us_encoder_s *enc, const us_stream_s *stream, const us_server_s *server) {
|
||||
# define SAY(x_msg, ...) fprintf(fp, x_msg "\n", ##__VA_ARGS__)
|
||||
SAY("\nuStreamer - Lightweight and fast MJPEG-HTTP streamer");
|
||||
SAY("═══════════════════════════════════════════════════");
|
||||
|
||||
@@ -112,9 +112,10 @@ void us_stream_loop(us_stream_s *stream) {
|
||||
if (!ready_wr->job_failed) {
|
||||
if (ready_wr->job_timely) {
|
||||
_stream_expose_frame(stream, ready_job->dest, captured_fps);
|
||||
US_LOG_PERF("##### Encoded frame exposed; worker=%s", ready_wr->name);
|
||||
US_LOG_PERF("##### Encoded JPEG exposed; worker=%s, latency=%.3Lf",
|
||||
ready_wr->name, us_get_now_monotonic() - ready_job->dest->grab_ts);
|
||||
} else {
|
||||
US_LOG_PERF("----- Encoded frame dropped; worker=%s", ready_wr->name);
|
||||
US_LOG_PERF("----- Encoded JPEG dropped; worker=%s", ready_wr->name);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
|
||||
@@ -157,7 +157,7 @@ void us_workers_pool_assign(us_workers_pool_s *pool, us_worker_s *ready_wr/*, vo
|
||||
US_MUTEX_UNLOCK(pool->free_workers_mutex);
|
||||
}
|
||||
|
||||
long double us_workers_pool_get_fluency_delay(us_workers_pool_s *pool, us_worker_s *ready_wr) {
|
||||
long double us_workers_pool_get_fluency_delay(us_workers_pool_s *pool, const us_worker_s *ready_wr) {
|
||||
const long double approx_job_time = pool->approx_job_time * 0.9 + ready_wr->last_job_time * 0.1;
|
||||
|
||||
US_LOG_VERBOSE("Correcting pool's %s approx_job_time: %.3Lf -> %.3Lf (last_job_time=%.3Lf)",
|
||||
|
||||
@@ -92,4 +92,4 @@ void us_workers_pool_destroy(us_workers_pool_s *pool);
|
||||
us_worker_s *us_workers_pool_wait(us_workers_pool_s *pool);
|
||||
void us_workers_pool_assign(us_workers_pool_s *pool, us_worker_s *ready_wr/*, void *job*/);
|
||||
|
||||
long double us_workers_pool_get_fluency_delay(us_workers_pool_s *pool, us_worker_s *ready_wr);
|
||||
long double us_workers_pool_get_fluency_delay(us_workers_pool_s *pool, const us_worker_s *ready_wr);
|
||||
|
||||
Reference in New Issue
Block a user