Compare commits

...

7 Commits
v5.24 ... v5.26

Author SHA1 Message Date
Maxim Devaev
f83ff439c4 Bump version: 5.25 → 5.26 2022-11-01 18:10:19 +03:00
Maxim Devaev
983796e952 request keyframe via sink 2022-11-01 16:36:10 +03:00
Maxim Devaev
40be0c20e2 Bump version: 5.24 → 5.25 2022-10-31 16:34:13 +03:00
Maxim Devaev
f1e9d4568c Fixed #169: Userspace fix for insufficient size of H.264 buffer 2022-10-31 16:33:34 +03:00
Maxim Devaev
f4f57cce38 prettify 2022-10-22 07:01:21 +03:00
Michael Lynch
c87ad5703c Update H264 streaming guide to include audio streaming (#181)
* Support multiple (audio+video) streams in demo janus client (#4)

* Support multiple (audio+video) streams in demo janus client

* Adjust wording in H264 guide

* Use consistent braces style

Co-authored-by: Louis Goessling <louis@goessling.com>
2022-10-22 04:43:38 +03:00
tomaszduda23
95df13b7cb Update docker documentation (#180)
* Update README.md

* Update README.md

* replace NO_EDID=1 with EDID=1

* doc update
2022-10-22 04:41:54 +03:00
23 changed files with 123 additions and 36 deletions

View File

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

View File

@@ -59,8 +59,8 @@ jobs:
- -
name: Test name: Test
run: | run: |
echo version: $(docker run --rm -e NO_EDID=1 -t ustreamer --version) echo version: $(docker run --rm -t ustreamer --version)
echo -e "features:\n$(docker run --rm -e NO_EDID=1 -t ustreamer --features)" echo -e "features:\n$(docker run --rm -t ustreamer --features)"
- -
name: Build multi arch name: Build multi arch
uses: docker/build-push-action@v3 uses: docker/build-push-action@v3

View File

@@ -81,35 +81,57 @@ $ ./ustreamer \
You can always view the full list of options with ```ustreamer --help```. You can always view the full list of options with ```ustreamer --help```.
-----
# Docker (Raspberry Pi 4 HDMI) # Docker (Raspberry Pi 4 HDMI)
## Preparations
## Preparations
Add following lines to /boot/firmware/usercfg.txt: Add following lines to /boot/firmware/usercfg.txt:
``` ```
gpu_mem=128 gpu_mem=128
dtoverlay=tc358743 dtoverlay=tc358743
``` ```
Check size of CMA: Check size of CMA:
```bash ```bash
$ dmesg | grep cma-reserved $ 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) [ 0.000000] Memory: 7700524K/8244224K available (11772K kernel code, 1278K rwdata, 4320K rodata, 4096K init, 1077K bss, 281556K reserved, 262144K cma-reserved)
``` ```
If it is smaller than 128M add following to /boot/firmware/cmdline.txt: If it is smaller than 128M add following to /boot/firmware/cmdline.txt:
``` ```
cma=128M cma=128M
``` ```
Save changes and reboot. Save changes and reboot.
## Launch ## Launch
Start container: Start container:
```bash ```bash
$ docker run --device /dev/video0:/dev/video0 -p 8080:8080 pikvm/ustreamer:latest $ 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). 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 \
--persistent \
--dv-timings \
--drop-same-frames=30
```
## EDID ## EDID
Container will set HDMI EDID before starging ustreamer. Use `-e NO_EDID=1` to not set EDID. Use `-e EDID_HEX=xx` to specify custom EDID data. Add `-e EDID=1` to set HDMI EDID before starging ustreamer. Use together with `-e EDID_HEX=xx` to specify custom EDID data.
----- -----
# Raspberry Pi Camera Example # Raspberry Pi Camera Example
Example usage for the Raspberry Pi v1 camera: Example usage for the Raspberry Pi v1 camera:
```bash ```bash
$ sudo modprobe bcm2835-v4l2 $ sudo modprobe bcm2835-v4l2

View File

@@ -78,6 +78,17 @@ memsink: {
EOF EOF
``` ```
If you're using a TC358743-based video capture device that supports audio capture, run the following command to enable audio streaming:
```sh
cat << EOF >> /opt/janus/lib/janus/configs/janus.plugin.ustreamer.jcfg
audio: {
device = "hw:1"
tc358743 = "/dev/video0"
}
EOF
```
### Start µStreamer and the Janus WebRTC Server ### Start µStreamer and the Janus WebRTC Server
For µStreamer to share the video stream with the µStreamer Janus plugin, µStreamer must run with the following command-line flags: For µStreamer to share the video stream with the µStreamer Janus plugin, µStreamer must run with the following command-line flags:
@@ -111,13 +122,14 @@ The client-side JavaScript application uses the following control flow:
1. The client instructs the Janus server to attach the µStreamer Janus plugin. 1. The client instructs the Janus server to attach the µStreamer Janus plugin.
1. On success, the client obtains a plugin handle through which it can send requests directly to the µStreamer Janus plugin. The client processes responses via the `attach` callbacks: 1. On success, the client obtains a plugin handle through which it can send requests directly to the µStreamer Janus plugin. The client processes responses via the `attach` callbacks:
- `onmessage` for general messages - `onmessage` for general messages
- `onremotetrack` for the H.264 video stream - `onremotetrack` for the H.264 video stream and (optionally) an Opus audio stream
1. The client issues a `watch` request to the µStreamer Janus plugin, which initiates the H.264 stream in the plugin itself. 1. The client issues a `watch` request to the µStreamer Janus plugin, which initiates the H.264 stream in the plugin itself.
- It takes a few seconds for uStreamer's video stream to become available to Janus. The first `watch` request may fail, so the client must retry the `watch` request. - It takes a few seconds for uStreamer's video stream to become available to Janus. The first `watch` request may fail, so the client must retry the `watch` request.
1. The client and server negotiate the underlying parameters of the WebRTC session. This procedure is called JavaScript Session Establishment Protocol (JSEP). The server makes a `jsepOffer` to the client, and the client responds with a `jsepAnswer`. 1. The client and server negotiate the underlying parameters of the WebRTC session. This procedure is called JavaScript Session Establishment Protocol (JSEP). The server makes a `jsepOffer` to the client, and the client responds with a `jsepAnswer`.
1. The client issues a `start` request to the µStreamer Janus plugin to indicate that the client wants to begin consuming the video stream. 1. The client issues a `start` request to the µStreamer Janus plugin to indicate that the client wants to begin consuming the video stream.
1. The µStreamer Janus plugin delivers the H.264 video stream to the client via WebRTC. 1. The µStreamer Janus plugin delivers the H.264 video stream and (optionally) an Opus audio stream to the client via WebRTC.
1. The Janus client library invokes the `onremotetrack` callback. The client attaches the video stream to the `<video>` element, rendering the video stream in the browser window. 1. The Janus client library invokes the `onremotetrack` callback with the video stream. The client attaches the video stream to the `<video>` element, rendering the video stream in the browser window.
1. (if an audio track is available) The Janus client library invokes the `onremotetrack` callback with the Opus audio stream. The client adds the audio stream to the `<video>` element, rendering the audio in the browser window.
### Sample Code ### Sample Code
@@ -176,7 +188,7 @@ The client-side JavaScript application uses the following control flow:
// successfully. // successfully.
success: function (pluginHandle) { success: function (pluginHandle) {
uStreamerPluginHandle = pluginHandle; uStreamerPluginHandle = pluginHandle;
// Instruct the µStreamer Janus plugin to initiate the video stream. // Instruct the µStreamer Janus plugin to initiate streaming.
uStreamerPluginHandle.send({ message: { request: "watch" } }); uStreamerPluginHandle.send({ message: { request: "watch" } });
}, },
@@ -211,16 +223,17 @@ The client-side JavaScript application uses the following control flow:
} }
}, },
// Callback function, for when the video stream arrives. // Callback function, for when a media stream arrives.
onremotetrack: function (mediaStreamTrack, mediaId, isAdded) { onremotetrack: function (mediaStreamTrack, mediaId, isAdded) {
if (isAdded) { if (isAdded) {
// Attach the received media track to the video element. Cloning the // Attach the received media track to the video element. Cloning the
// mediaStreamTrack creates a new object with a distinct, globally // mediaStreamTrack creates a new object with a distinct, globally
// unique stream identifier. // unique stream identifier.
const videoElement = document.getElementById("webrtc-output"); const videoElement = document.getElementById("webrtc-output");
const stream = new MediaStream(); if (videoElement.srcObject === null) {
stream.addTrack(mediaStreamTrack.clone()); videoElement.srcObject = new MediaStream();
videoElement.srcObject = stream; }
videoElement.srcObject.addTrack(mediaStreamTrack.clone());
} }
}, },
}); });

View File

@@ -46,12 +46,15 @@ int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s* mem, uint64_t last_id)
return -2; return -2;
} }
us_frame_s *us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, uint64_t *frame_id) { us_frame_s *us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, uint64_t *frame_id, bool key_required) {
us_frame_s *frame = us_frame_init(); us_frame_s *frame = us_frame_init();
us_frame_set_data(frame, mem->data, mem->used); us_frame_set_data(frame, mem->data, mem->used);
US_FRAME_COPY_META(mem, frame); US_FRAME_COPY_META(mem, frame);
*frame_id = mem->id; *frame_id = mem->id;
mem->last_client_ts = us_get_now_monotonic(); mem->last_client_ts = us_get_now_monotonic();
if (key_required) {
mem->key_requested = true;
}
bool ok = true; bool ok = true;
if (frame->format != V4L2_PIX_FMT_H264) { if (frame->format != V4L2_PIX_FMT_H264) {

View File

@@ -22,6 +22,7 @@
#pragma once #pragma once
#include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <unistd.h> #include <unistd.h>
@@ -35,4 +36,4 @@
int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s* mem, uint64_t last_id); int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s* mem, uint64_t last_id);
us_frame_s *us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, uint64_t *frame_id); us_frame_s *us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, uint64_t *frame_id, bool key_required);

View File

@@ -76,6 +76,7 @@ static pthread_mutex_t _g_audio_lock = PTHREAD_MUTEX_INITIALIZER;
static atomic_bool _g_ready = false; static atomic_bool _g_ready = false;
static atomic_bool _g_stop = false; static atomic_bool _g_stop = false;
static atomic_bool _g_has_watchers = false; static atomic_bool _g_has_watchers = false;
static atomic_bool _g_key_required = false;
#define _LOCK_VIDEO US_MUTEX_LOCK(_g_video_lock) #define _LOCK_VIDEO US_MUTEX_LOCK(_g_video_lock)
@@ -149,10 +150,13 @@ static void *_video_sink_thread(UNUSED void *arg) {
while (!_STOP && _HAS_WATCHERS) { while (!_STOP && _HAS_WATCHERS) {
const int result = us_memsink_fd_wait_frame(fd, mem, frame_id); const int result = us_memsink_fd_wait_frame(fd, mem, frame_id);
if (result == 0) { if (result == 0) {
us_frame_s *const frame = us_memsink_fd_get_frame(fd, mem, &frame_id); us_frame_s *const frame = us_memsink_fd_get_frame(fd, mem, &frame_id, atomic_load(&_g_key_required));
if (frame == NULL) { if (frame == NULL) {
goto close_memsink; goto close_memsink;
} }
// if (frame->key) {
// atomic_store(&_g_key_required, false);
// }
if (us_queue_put(_g_video_queue, frame, 0) != 0) { if (us_queue_put(_g_video_queue, frame, 0) != 0) {
_IF_NOT_REPORTED({ US_JLOG_PERROR("video", "Video queue is full"); }); _IF_NOT_REPORTED({ US_JLOG_PERROR("video", "Video queue is full"); });
us_frame_destroy(frame); us_frame_destroy(frame);
@@ -423,6 +427,7 @@ static struct janus_plugin_result *_plugin_handle_message(
} else if (!strcmp(request_str, "watch")) { } else if (!strcmp(request_str, "watch")) {
char *sdp; char *sdp;
{ {
// atomic_store(&_g_key_required, true);
char *const video_sdp = us_rtpv_make_sdp(_g_rtpv); char *const video_sdp = us_rtpv_make_sdp(_g_rtpv);
if (video_sdp == NULL) { if (video_sdp == NULL) {
PUSH_ERROR(503, "Haven't received SPS/PPS from memsink yet"); PUSH_ERROR(503, "Haven't received SPS/PPS from memsink yet");

View File

@@ -1,6 +1,6 @@
.\" Manpage for ustreamer-dump. .\" Manpage for ustreamer-dump.
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos .\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
.TH USTREAMER-DUMP 1 "version 5.24" "January 2021" .TH USTREAMER-DUMP 1 "version 5.26" "January 2021"
.SH NAME .SH NAME
ustreamer-dump \- Dump uStreamer's memory sink to file ustreamer-dump \- Dump uStreamer's memory sink to file
@@ -44,6 +44,9 @@ Limit the number of frames. Default: 0 (infinite).
.TP .TP
.BR \-i ", "\-\-interval\ \fIsec .BR \-i ", "\-\-interval\ \fIsec
Delay between reading frames (float). Default: 0. Delay between reading frames (float). Default: 0.
.TP
.BR \-k ", " \-\-key
Request keyframe from the sink. Default: disabled.
.SS "Logging options" .SS "Logging options"
.TP .TP

View File

@@ -1,6 +1,6 @@
.\" Manpage for ustreamer. .\" Manpage for ustreamer.
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos .\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
.TH USTREAMER 1 "version 5.24" "November 2020" .TH USTREAMER 1 "version 5.26" "November 2020"
.SH NAME .SH NAME
ustreamer \- stream MJPEG video from any V4L2 device to the network ustreamer \- stream MJPEG video from any V4L2 device to the network

View File

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

View File

@@ -2,7 +2,7 @@
set -e set -e
[ -z "$NO_EDID" ] && { [ -n "$EDID" ] && {
[ -n "$EDID_HEX" ] && echo "$EDID_HEX" > /edid.hex [ -n "$EDID_HEX" ] && echo "$EDID_HEX" > /edid.hex
while true; do while true; do
v4l2-ctl --device=/dev/video0 --set-edid=file=/edid.hex --fix-edid-checksums --info-edid && break v4l2-ctl --device=/dev/video0 --set-edid=file=/edid.hex --fix-edid-checksums --info-edid && break

View File

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

View File

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

View File

@@ -181,12 +181,18 @@ static int _wait_frame(_MemsinkObject *self) {
return -2; return -2;
} }
static PyObject *_MemsinkObject_wait_frame(_MemsinkObject *self, PyObject *Py_UNUSED(ignored)) { static PyObject *_MemsinkObject_wait_frame(_MemsinkObject *self, PyObject *args, PyObject *kwargs) {
if (self->mem == NULL || self->fd <= 0) { if (self->mem == NULL || self->fd <= 0) {
PyErr_SetString(PyExc_RuntimeError, "Closed"); PyErr_SetString(PyExc_RuntimeError, "Closed");
return NULL; return NULL;
} }
bool key_required = false;
static char *kws[] = {"key_required", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|p", kws, &key_required)) {
return NULL;
}
switch (_wait_frame(self)) { switch (_wait_frame(self)) {
case 0: break; case 0: break;
case -2: Py_RETURN_NONE; case -2: Py_RETURN_NONE;
@@ -198,6 +204,9 @@ static PyObject *_MemsinkObject_wait_frame(_MemsinkObject *self, PyObject *Py_UN
self->frame_id = _MEM(id); self->frame_id = _MEM(id);
self->frame_ts = us_get_now_monotonic(); self->frame_ts = us_get_now_monotonic();
_MEM(last_client_ts) = self->frame_ts; _MEM(last_client_ts) = self->frame_ts;
if (key_required) {
_MEM(key_requested) = true;
}
if (flock(self->fd, LOCK_UN) < 0) { if (flock(self->fd, LOCK_UN) < 0) {
return PyErr_SetFromErrno(PyExc_OSError); return PyErr_SetFromErrno(PyExc_OSError);
@@ -260,7 +269,7 @@ static PyMethodDef _MemsinkObject_methods[] = {
ADD_METHOD("close", close, METH_NOARGS), ADD_METHOD("close", close, METH_NOARGS),
ADD_METHOD("__enter__", enter, METH_NOARGS), ADD_METHOD("__enter__", enter, METH_NOARGS),
ADD_METHOD("__exit__", exit, METH_VARARGS), ADD_METHOD("__exit__", exit, METH_VARARGS),
ADD_METHOD("wait_frame", wait_frame, METH_NOARGS), ADD_METHOD("wait_frame", wait_frame, METH_VARARGS | METH_KEYWORDS),
ADD_METHOD("is_opened", is_opened, METH_NOARGS), ADD_METHOD("is_opened", is_opened, METH_NOARGS),
{}, {},
# undef ADD_METHOD # undef ADD_METHOD

View File

@@ -48,6 +48,7 @@ enum _OPT_VALUES {
_O_OUTPUT_JSON = 'j', _O_OUTPUT_JSON = 'j',
_O_COUNT = 'c', _O_COUNT = 'c',
_O_INTERVAL = 'i', _O_INTERVAL = 'i',
_O_KEY = 'k',
_O_HELP = 'h', _O_HELP = 'h',
_O_VERSION = 'v', _O_VERSION = 'v',
@@ -67,6 +68,7 @@ static const struct option _LONG_OPTS[] = {
{"output-json", no_argument, NULL, _O_OUTPUT_JSON}, {"output-json", no_argument, NULL, _O_OUTPUT_JSON},
{"count", required_argument, NULL, _O_COUNT}, {"count", required_argument, NULL, _O_COUNT},
{"interval", required_argument, NULL, _O_INTERVAL}, {"interval", required_argument, NULL, _O_INTERVAL},
{"key", no_argument, NULL, _O_KEY},
{"log-level", required_argument, NULL, _O_LOG_LEVEL}, {"log-level", required_argument, NULL, _O_LOG_LEVEL},
{"perf", no_argument, NULL, _O_PERF}, {"perf", no_argument, NULL, _O_PERF},
@@ -98,6 +100,7 @@ static void _install_signal_handlers(void);
static int _dump_sink( static int _dump_sink(
const char *sink_name, unsigned sink_timeout, const char *sink_name, unsigned sink_timeout,
long long count, long double interval, long long count, long double interval,
bool key_required,
_output_context_s *ctx); _output_context_s *ctx);
static void _help(FILE *fp); static void _help(FILE *fp);
@@ -113,6 +116,7 @@ int main(int argc, char *argv[]) {
bool output_json = false; bool output_json = false;
long long count = 0; long long count = 0;
long double interval = 0; long double interval = 0;
bool key_required = false;
# define OPT_SET(_dest, _value) { \ # define OPT_SET(_dest, _value) { \
_dest = _value; \ _dest = _value; \
@@ -150,6 +154,7 @@ int main(int argc, char *argv[]) {
case _O_OUTPUT_JSON: OPT_SET(output_json, true); case _O_OUTPUT_JSON: OPT_SET(output_json, true);
case _O_COUNT: OPT_NUMBER("--count", count, 0, LLONG_MAX, 0); case _O_COUNT: OPT_NUMBER("--count", count, 0, LLONG_MAX, 0);
case _O_INTERVAL: OPT_LDOUBLE("--interval", interval, 0, 60); case _O_INTERVAL: OPT_LDOUBLE("--interval", interval, 0, 60);
case _O_KEY: OPT_SET(key_required, true);
case _O_LOG_LEVEL: OPT_NUMBER("--log-level", us_g_log_level, US_LOG_LEVEL_INFO, US_LOG_LEVEL_DEBUG, 0); case _O_LOG_LEVEL: OPT_NUMBER("--log-level", us_g_log_level, US_LOG_LEVEL_INFO, US_LOG_LEVEL_DEBUG, 0);
case _O_PERF: OPT_SET(us_g_log_level, US_LOG_LEVEL_PERF); case _O_PERF: OPT_SET(us_g_log_level, US_LOG_LEVEL_PERF);
@@ -186,7 +191,7 @@ int main(int argc, char *argv[]) {
} }
_install_signal_handlers(); _install_signal_handlers();
const int retval = abs(_dump_sink(sink_name, sink_timeout, count, interval, &ctx)); const int retval = abs(_dump_sink(sink_name, sink_timeout, count, interval, key_required, &ctx));
if (ctx.v_output && ctx.destroy) { if (ctx.v_output && ctx.destroy) {
ctx.destroy(ctx.v_output); ctx.destroy(ctx.v_output);
} }
@@ -223,6 +228,7 @@ static void _install_signal_handlers(void) {
static int _dump_sink( static int _dump_sink(
const char *sink_name, unsigned sink_timeout, const char *sink_name, unsigned sink_timeout,
long long count, long double interval, long long count, long double interval,
bool key_required,
_output_context_s *ctx) { _output_context_s *ctx) {
if (count == 0) { if (count == 0) {
@@ -245,8 +251,10 @@ static int _dump_sink(
long double last_ts = 0; long double last_ts = 0;
while (!_g_stop) { while (!_g_stop) {
int error = us_memsink_client_get(sink, frame); const int error = us_memsink_client_get(sink, frame, key_required);
if (error == 0) { if (error == 0) {
key_required = false;
const long double now = us_get_now_monotonic(); const long double now = us_get_now_monotonic();
const long long now_second = us_floor_ms(now); const long long now_second = us_floor_ms(now);
@@ -322,6 +330,7 @@ static void _help(FILE *fp) {
SAY(" -j|--output-json ──────── Format output as JSON. Required option --output. Default: disabled.\n"); SAY(" -j|--output-json ──────── Format output as JSON. Required option --output. Default: disabled.\n");
SAY(" -c|--count <N> ───────── Limit the number of frames. Default: 0 (infinite).\n"); SAY(" -c|--count <N> ───────── Limit the number of frames. Default: 0 (infinite).\n");
SAY(" -i|--interval <sec> ───── Delay between reading frames (float). Default: 0.\n"); SAY(" -i|--interval <sec> ───── Delay between reading frames (float). Default: 0.\n");
SAY(" -k|--key ──────────────── Request keyframe from the sink. Default: disabled.\n");
SAY("Logging options:"); SAY("Logging options:");
SAY("════════════════"); SAY("════════════════");
SAY(" --log-level <N> ──── Verbosity level of messages from 0 (info) to 3 (debug)."); SAY(" --log-level <N> ──── Verbosity level of messages from 0 (info) to 3 (debug).");

View File

@@ -23,7 +23,7 @@
#pragma once #pragma once
#define US_VERSION_MAJOR 5 #define US_VERSION_MAJOR 5
#define US_VERSION_MINOR 24 #define US_VERSION_MINOR 26
#define US_MAKE_VERSION2(_major, _minor) #_major "." #_minor #define US_MAKE_VERSION2(_major, _minor) #_major "." #_minor
#define US_MAKE_VERSION1(_major, _minor) US_MAKE_VERSION2(_major, _minor) #define US_MAKE_VERSION1(_major, _minor) US_MAKE_VERSION2(_major, _minor)

View File

@@ -117,7 +117,7 @@ bool us_memsink_server_check(us_memsink_s *sink, const us_frame_s *frame) {
return (has_clients || !US_FRAME_COMPARE_META_USED_NOTS(sink->mem, frame));; return (has_clients || !US_FRAME_COMPARE_META_USED_NOTS(sink->mem, frame));;
} }
int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame) { int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *const key_requested) {
assert(sink->server); assert(sink->server);
const long double now = us_get_now_monotonic(); const long double now = us_get_now_monotonic();
@@ -133,6 +133,12 @@ int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame) {
sink->last_id = us_get_now_id(); sink->last_id = us_get_now_id();
sink->mem->id = sink->last_id; sink->mem->id = sink->last_id;
if (sink->mem->key_requested && frame->key) {
sink->mem->key_requested = false;
}
if (key_requested != NULL) {
*key_requested = sink->mem->key_requested;
}
memcpy(sink->mem->data, frame->data, frame->used); memcpy(sink->mem->data, frame->data, frame->used);
sink->mem->used = frame->used; sink->mem->used = frame->used;
@@ -140,6 +146,7 @@ int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame) {
sink->mem->magic = US_MEMSINK_MAGIC; sink->mem->magic = US_MEMSINK_MAGIC;
sink->mem->version = US_MEMSINK_VERSION; sink->mem->version = US_MEMSINK_VERSION;
atomic_store(&sink->has_clients, (sink->mem->last_client_ts + sink->client_ttl > us_get_now_monotonic())); atomic_store(&sink->has_clients, (sink->mem->last_client_ts + sink->client_ttl > us_get_now_monotonic()));
if (flock(sink->fd, LOCK_UN) < 0) { if (flock(sink->fd, LOCK_UN) < 0) {
@@ -159,7 +166,7 @@ int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame) {
return 0; return 0;
} }
int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame) { // cppcheck-suppress unusedFunction int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool key_required) { // cppcheck-suppress unusedFunction
assert(!sink->server); // Client only assert(!sink->server); // Client only
if (us_flock_timedwait_monotonic(sink->fd, sink->timeout) < 0) { if (us_flock_timedwait_monotonic(sink->fd, sink->timeout) < 0) {
@@ -185,6 +192,9 @@ int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame) { // cppcheck-s
retval = 0; retval = 0;
} }
sink->mem->last_client_ts = us_get_now_monotonic(); sink->mem->last_client_ts = us_get_now_monotonic();
if (key_required) {
sink->mem->key_requested = true;
}
} }
done: done:

View File

@@ -63,6 +63,6 @@ us_memsink_s *us_memsink_init(
void us_memsink_destroy(us_memsink_s *sink); void us_memsink_destroy(us_memsink_s *sink);
bool us_memsink_server_check(us_memsink_s *sink, const us_frame_s *frame); bool us_memsink_server_check(us_memsink_s *sink, const us_frame_s *frame);
int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame); int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *const key_requested);
int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame); int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool key_required);

View File

@@ -30,7 +30,7 @@
#define US_MEMSINK_MAGIC ((uint64_t)0xCAFEBABECAFEBABE) #define US_MEMSINK_MAGIC ((uint64_t)0xCAFEBABECAFEBABE)
#define US_MEMSINK_VERSION ((uint32_t)2) #define US_MEMSINK_VERSION ((uint32_t)3)
#ifndef US_CFG_MEMSINK_MAX_DATA #ifndef US_CFG_MEMSINK_MAX_DATA
# define US_CFG_MEMSINK_MAX_DATA 33554432 # define US_CFG_MEMSINK_MAX_DATA 33554432
@@ -57,6 +57,7 @@ typedef struct {
long double encode_end_ts; long double encode_end_ts;
long double last_client_ts; long double last_client_ts;
bool key_requested;
uint8_t data[US_MEMSINK_MAX_DATA]; uint8_t data[US_MEMSINK_MAX_DATA];
} us_memsink_shared_s; } us_memsink_shared_s;

View File

@@ -56,9 +56,15 @@ void us_h264_stream_process(us_h264_stream_s *h264, const us_frame_s *frame, boo
US_LOG_VERBOSE("H264: JPEG decoded; time=%.3Lf", us_get_now_monotonic() - now); US_LOG_VERBOSE("H264: JPEG decoded; time=%.3Lf", us_get_now_monotonic() - now);
} }
if (h264->key_requested) {
US_LOG_INFO("H264: Requested keyframe by a sink client");
h264->key_requested = false;
force_key = true;
}
bool online = false; bool online = false;
if (!us_m2m_encoder_compress(h264->enc, frame, h264->dest, force_key)) { if (!us_m2m_encoder_compress(h264->enc, frame, h264->dest, force_key)) {
online = !us_memsink_server_put(h264->sink, h264->dest); online = !us_memsink_server_put(h264->sink, h264->dest, &h264->key_requested);
} }
atomic_store(&h264->online, online); atomic_store(&h264->online, online);
} }

View File

@@ -36,6 +36,7 @@
typedef struct { typedef struct {
us_memsink_s *sink; us_memsink_s *sink;
bool key_requested;
us_frame_s *tmp_src; us_frame_s *tmp_src;
us_frame_s *dest; us_frame_s *dest;
us_m2m_encoder_s *enc; us_m2m_encoder_s *enc;

View File

@@ -220,7 +220,11 @@ static void _m2m_encoder_prepare(us_m2m_encoder_s *enc, const us_frame_s *frame)
fmt.fmt.pix_mp.colorspace = V4L2_COLORSPACE_DEFAULT; fmt.fmt.pix_mp.colorspace = V4L2_COLORSPACE_DEFAULT;
fmt.fmt.pix_mp.num_planes = 1; fmt.fmt.pix_mp.num_planes = 1;
// fmt.fmt.pix_mp.plane_fmt[0].bytesperline = 0; // fmt.fmt.pix_mp.plane_fmt[0].bytesperline = 0;
// fmt.fmt.pix_mp.plane_fmt[0].sizeimage = 512 << 10; if (enc->output_format == V4L2_PIX_FMT_H264) {
// https://github.com/pikvm/ustreamer/issues/169
// https://github.com/raspberrypi/linux/pull/5232
fmt.fmt.pix_mp.plane_fmt[0].sizeimage = (1024 + 512) << 10; // 1.5Mb
}
_E_LOG_DEBUG("Configuring OUTPUT format ..."); _E_LOG_DEBUG("Configuring OUTPUT format ...");
_E_XIOCTL(VIDIOC_S_FMT, &fmt, "Can't set OUTPUT format"); _E_XIOCTL(VIDIOC_S_FMT, &fmt, "Can't set OUTPUT format");
if (fmt.fmt.pix_mp.pixelformat != enc->output_format) { if (fmt.fmt.pix_mp.pixelformat != enc->output_format) {

View File

@@ -32,7 +32,7 @@ static void _stream_expose_frame(us_stream_s *stream, us_frame_s *frame, unsigne
#define _SINK_PUT(x_sink, x_frame) { \ #define _SINK_PUT(x_sink, x_frame) { \
if (stream->x_sink && us_memsink_server_check(stream->x_sink, x_frame)) {\ if (stream->x_sink && us_memsink_server_check(stream->x_sink, x_frame)) {\
us_memsink_server_put(stream->x_sink, x_frame); \ us_memsink_server_put(stream->x_sink, x_frame, NULL); \
} \ } \
} }