Compare commits

...

22 Commits
v5.24 ... v5.30

Author SHA1 Message Date
Maxim Devaev
3d2f254f40 Bump version: 5.29 → 5.30 2022-11-05 23:50:17 +03:00
Maxim Devaev
13e31d0cd5 janus: handle PLI video 2022-11-05 22:57:22 +03:00
Maxim Devaev
4e7676f307 refactoring 2022-11-05 22:45:18 +03:00
Maxim Devaev
95fcc3c58e Bump version: 5.28 → 5.29 2022-11-04 19:48:26 +03:00
Maxim Devaev
81500af1b3 renamed --key to --key-required 2022-11-04 19:47:50 +03:00
Maxim Devaev
86a2141361 janus: key_required handler 2022-11-04 13:51:49 +03:00
Maxim Devaev
22e5c8627b removed sps/pps from sdp 2022-11-03 22:54:06 +03:00
Maxim Devaev
c8600e62c2 Bump version: 5.27 → 5.28 2022-11-03 19:12:35 +03:00
Maxim Devaev
335f19f0e3 refactoring 2022-11-03 19:11:58 +03:00
Michael Lynch
a24e0eeb86 Document additional required audio packages (#184) 2022-11-03 19:11:26 +03:00
Michael Lynch
3fcb8d3ee5 Document additional required audio packages (#184) 2022-11-03 19:10:17 +03:00
Michael Lynch
ca30656bf8 Assign stream index on outgoing RTP packets (#182)
* Assign stream index on outgoing RTP packets (#5)

* Correctly assign mindex on outgoing rtp packets

Previously mindex was not set and defaulted to zero. This lead to most packets
getting dropped because of sequence number reuse when streaming both audio and
video. This reorders the SDP entries for video and audio so that video is first
in both a video-only and a audio+video configuration. This means that the mindex
for video packets should always be zero, and for audio (if present) should
always be one. This assumes there will never be an audio-only configuration.

* Adjust comments

Co-authored-by: Michael Lynch <git@mtlynch.io>

* Add preprocessor conditional to guard packet.mindex setting

The mindex field wasn't added to the janus_plugin_rtp_packet until Janus 1.0, so this change adds a precompiler check to ensure JANUS_PLUGIN_API_VERSION is >= 100 before assigning a value to the mindex field.

* Preserve audio-then-video ordering for Janus 0.x

Co-authored-by: Louis Goessling <louis@goessling.com>
2022-11-03 19:04:15 +03:00
Michael Lynch
4f0abf7eec Assign stream index on outgoing RTP packets (#182)
* Assign stream index on outgoing RTP packets (#5)

* Correctly assign mindex on outgoing rtp packets

Previously mindex was not set and defaulted to zero. This lead to most packets
getting dropped because of sequence number reuse when streaming both audio and
video. This reorders the SDP entries for video and audio so that video is first
in both a video-only and a audio+video configuration. This means that the mindex
for video packets should always be zero, and for audio (if present) should
always be one. This assumes there will never be an audio-only configuration.

* Adjust comments

Co-authored-by: Michael Lynch <git@mtlynch.io>

* Add preprocessor conditional to guard packet.mindex setting

The mindex field wasn't added to the janus_plugin_rtp_packet until Janus 1.0, so this change adds a precompiler check to ensure JANUS_PLUGIN_API_VERSION is >= 100 before assigning a value to the mindex field.

* Preserve audio-then-video ordering for Janus 0.x

Co-authored-by: Louis Goessling <louis@goessling.com>
2022-11-03 19:03:16 +03:00
Maxim Devaev
8b233a4c71 Bump version: 5.26 → 5.27 2022-11-01 22:30:04 +03:00
Maxim Devaev
8a81158276 improved dump 2022-11-01 20:59:16 +03:00
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
31 changed files with 171 additions and 112 deletions

View File

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

View File

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

View File

@@ -38,7 +38,7 @@ 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, ```libjpeg9```/```libjpeg-turbo``` and ```libbsd``` (only for Linux).
* Arch: `sudo pacman -S libevent libjpeg-turbo libutil-linux libbsd`.
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev libbsd-dev`. Add `libgpiod-dev` for `WITH_GPIO=1` and `libsystemd-dev` for `WITH_SYSTEMD=1`.
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev libbsd-dev`. Add `libgpiod-dev` for `WITH_GPIO=1` and `libsystemd-dev` for `WITH_SYSTEMD=1` and `libasound2-dev libspeex-dev libspeexdsp-dev libopus-dev` for `WITH_JANUS=1`.
* Debian/Ubuntu: `sudo apt install build-essential libevent-dev libjpeg-dev libbsd-dev`.
* Alpine: `sudo apk add libevent-dev libbsd-dev libjpeg-turbo-dev musl-dev`. Build with `WITH_PTHREAD_NP=0`.
@@ -81,35 +81,57 @@ $ ./ustreamer \
You can always view the full list of options with ```ustreamer --help```.
-----
# Docker (Raspberry Pi 4 HDMI)
## Preparations
## Preparations
Add following lines to /boot/firmware/usercfg.txt:
```
gpu_mem=128
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)
```
If it is smaller than 128M add following to /boot/firmware/cmdline.txt:
```
cma=128M
```
Save changes and reboot.
## Launch
Start container:
```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).
## Custom config
```bash
$ docker run --rm pikvm/ustreamer:latest \
--format=uyvy \
--workers=3 \
--persistent \
--dv-timings \
--drop-same-frames=30
```
## 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
Example usage for the Raspberry Pi v1 camera:
```bash
$ sudo modprobe bcm2835-v4l2

View File

@@ -78,6 +78,17 @@ memsink: {
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
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. On success, the client obtains a plugin handle through which it can send requests directly to the µStreamer Janus plugin. The client processes responses via the `attach` callbacks:
- `onmessage` for general messages
- `onremotetrack` for the H.264 video stream
- `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.
- It takes a few seconds for uStreamer's video stream to become available to Janus. The first `watch` request may fail, so the client must retry the `watch` request.
1. The client and server negotiate the underlying parameters of the WebRTC session. This procedure is called JavaScript Session Establishment Protocol (JSEP). The server makes a `jsepOffer` to the client, and the client responds with a `jsepAnswer`.
1. The client issues a `start` request to the µStreamer Janus plugin to indicate that the client wants to begin consuming the video stream.
1. The µStreamer Janus plugin delivers the H.264 video stream to the client via WebRTC.
1. The Janus client library invokes the `onremotetrack` callback. The client attaches the video stream to the `<video>` element, rendering the video stream in the browser window.
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 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
@@ -176,7 +188,7 @@ The client-side JavaScript application uses the following control flow:
// successfully.
success: function (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" } });
},
@@ -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) {
if (isAdded) {
// Attach the received media track to the video element. Cloning the
// mediaStreamTrack creates a new object with a distinct, globally
// unique stream identifier.
const videoElement = document.getElementById("webrtc-output");
const stream = new MediaStream();
stream.addTrack(mediaStreamTrack.clone());
videoElement.srcObject = stream;
if (videoElement.srcObject === null) {
videoElement.srcObject = new MediaStream();
}
videoElement.srcObject.addTrack(mediaStreamTrack.clone());
}
},
});

View File

@@ -102,6 +102,11 @@ static void *_common_thread(void *v_client, bool video) {
packet.video = rtp->video;
packet.buffer = (char *)rtp->datagram;
packet.length = rtp->used;
# if JANUS_PLUGIN_API_VERSION >= 100
// The uStreamer Janus plugin places video in stream index 0 and audio
// (if available) in stream index 1.
packet.mindex = (rtp->video ? 0 : 1);
# endif
janus_plugin_rtp_extensions_reset(&packet.extensions);
// FIXME: Это очень эффективный способ уменьшить задержку, но WebRTC стек в хроме и фоксе
// слишком корявый, чтобы обработать это, из-за чего на кейфреймах начинаются заикания.

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;
}
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_set_data(frame, mem->data, mem->used);
US_FRAME_COPY_META(mem, frame);
*frame_id = mem->id;
mem->last_client_ts = us_get_now_monotonic();
if (key_required) {
mem->key_requested = true;
}
bool ok = true;
if (frame->format != V4L2_PIX_FMT_H264) {

View File

@@ -22,6 +22,7 @@
#pragma once
#include <stdbool.h>
#include <stdint.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);
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

@@ -35,6 +35,7 @@
#include <pthread.h>
#include <jansson.h>
#include <janus/plugins/plugin.h>
#include <janus/rtcp.h>
#include "uslibs/const.h"
#include "uslibs/tools.h"
@@ -76,6 +77,7 @@ static pthread_mutex_t _g_audio_lock = PTHREAD_MUTEX_INITIALIZER;
static atomic_bool _g_ready = false;
static atomic_bool _g_stop = false;
static atomic_bool _g_has_watchers = false;
static atomic_bool _g_key_required = false;
#define _LOCK_VIDEO US_MUTEX_LOCK(_g_video_lock)
@@ -149,10 +151,13 @@ static void *_video_sink_thread(UNUSED void *arg) {
while (!_STOP && _HAS_WATCHERS) {
const int result = us_memsink_fd_wait_frame(fd, mem, frame_id);
if (result == 0) {
us_frame_s *const frame = us_memsink_fd_get_frame(fd, mem, &frame_id);
us_frame_s *const frame = us_memsink_fd_get_frame(fd, mem, &frame_id, atomic_load(&_g_key_required));
if (frame == NULL) {
goto close_memsink;
}
if (frame->key) {
atomic_store(&_g_key_required, false);
}
if (us_queue_put(_g_video_queue, frame, 0) != 0) {
_IF_NOT_REPORTED({ US_JLOG_PERROR("video", "Video queue is full"); });
us_frame_destroy(frame);
@@ -424,10 +429,6 @@ static struct janus_plugin_result *_plugin_handle_message(
char *sdp;
{
char *const video_sdp = us_rtpv_make_sdp(_g_rtpv);
if (video_sdp == NULL) {
PUSH_ERROR(503, "Haven't received SPS/PPS from memsink yet");
goto ok_wait;
}
char *const audio_sdp = (_g_rtpa ? us_rtpa_make_sdp(_g_rtpa) : us_strdup(""));
US_ASPRINTF(sdp,
"v=0" RN
@@ -435,7 +436,16 @@ static struct janus_plugin_result *_plugin_handle_message(
"s=PiKVM uStreamer" RN
"t=0 0" RN
"%s%s",
us_get_now_id() >> 1, audio_sdp, video_sdp
us_get_now_id() >> 1,
# if JANUS_PLUGIN_API_VERSION >= 100
// Place video SDP before audio SDP so that the video and audio streams
// have predictable indices, even if audio is not available.
// See also client.c.
video_sdp, audio_sdp
# else
// For versions of Janus prior to 1.x, place the audio SDP first.
audio_sdp, video_sdp
# endif
);
free(audio_sdp);
free(video_sdp);
@@ -445,6 +455,10 @@ static struct janus_plugin_result *_plugin_handle_message(
PUSH_STATUS("started", offer_jsep);
json_decref(offer_jsep);
} else if (!strcmp(request_str, "key_required")) {
// US_JLOG_INFO("main", "Got key_required message");
atomic_store(&_g_key_required, true);
} else {
PUSH_ERROR(405, "Not implemented");
}
@@ -458,6 +472,13 @@ static struct janus_plugin_result *_plugin_handle_message(
# undef FREE_MSG_JSEP
}
static void _plugin_incoming_rtcp(UNUSED janus_plugin_session *handle, UNUSED janus_plugin_rtcp *packet) {
if (packet->video && janus_rtcp_has_pli(packet->buffer, packet->length)) {
// US_JLOG_INFO("main", "Got video PLI");
atomic_store(&_g_key_required, true);
}
}
// ***** Plugin *****
@@ -469,10 +490,6 @@ static const char *_plugin_get_name(void) { return US_PLUGIN_NAME; }
static const char *_plugin_get_author(void) { return "Maxim Devaev <mdevaev@gmail.com>"; }
static const char *_plugin_get_package(void) { return US_PLUGIN_PACKAGE; }
static void _plugin_incoming_rtp(UNUSED janus_plugin_session *handle, UNUSED janus_plugin_rtp *packet) {
// Just a stub to avoid logging spam about the plugin's purpose
}
janus_plugin *create(void) {
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Woverride-init"
@@ -497,7 +514,7 @@ janus_plugin *create(void) {
.get_author = _plugin_get_author,
.get_package = _plugin_get_package,
.incoming_rtp = _plugin_incoming_rtp,
.incoming_rtcp = _plugin_incoming_rtcp,
);
# pragma GCC diagnostic pop
return &plugin;

View File

@@ -36,35 +36,15 @@ us_rtpv_s *us_rtpv_init(us_rtp_callback_f callback, bool zero_playout_delay) {
US_CALLOC(rtpv, 1);
rtpv->rtp = us_rtp_init(96, true, zero_playout_delay);
rtpv->callback = callback;
rtpv->sps = us_frame_init();
rtpv->pps = us_frame_init();
US_MUTEX_INIT(rtpv->mutex);
return rtpv;
}
void us_rtpv_destroy(us_rtpv_s *rtpv) {
US_MUTEX_DESTROY(rtpv->mutex);
us_frame_destroy(rtpv->pps);
us_frame_destroy(rtpv->sps);
us_rtp_destroy(rtpv->rtp);
free(rtpv);
}
char *us_rtpv_make_sdp(us_rtpv_s *rtpv) {
US_MUTEX_LOCK(rtpv->mutex);
if (rtpv->sps->used == 0 || rtpv->pps->used == 0) {
US_MUTEX_UNLOCK(rtpv->mutex);
return NULL;
}
char *sps = NULL;
char *pps = NULL;
us_base64_encode(rtpv->sps->data, rtpv->sps->used, &sps, NULL);
us_base64_encode(rtpv->pps->data, rtpv->pps->used, &pps, NULL);
US_MUTEX_UNLOCK(rtpv->mutex);
# define PAYLOAD rtpv->rtp->payload
// https://tools.ietf.org/html/rfc6184
// https://github.com/meetecho/janus-gateway/issues/2443
@@ -75,8 +55,6 @@ char *us_rtpv_make_sdp(us_rtpv_s *rtpv) {
"a=rtpmap:%u H264/90000" RN
"a=fmtp:%u profile-level-id=42E01F" RN
"a=fmtp:%u packetization-mode=1" RN
"a=fmtp:%u sprop-sps=%s" RN
"a=fmtp:%u sprop-pps=%s" RN
"a=rtcp-fb:%u nack" RN
"a=rtcp-fb:%u nack pli" RN
"a=rtcp-fb:%u goog-remb" RN
@@ -84,17 +62,12 @@ char *us_rtpv_make_sdp(us_rtpv_s *rtpv) {
"%s" // playout-delay
"a=sendonly" RN,
PAYLOAD, PAYLOAD, PAYLOAD, PAYLOAD,
PAYLOAD, sps,
PAYLOAD, pps,
PAYLOAD, PAYLOAD, PAYLOAD,
rtpv->rtp->ssrc,
(rtpv->rtp->zero_playout_delay ? "a=extmap:1 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay" RN : "")
);
# undef PAYLOAD
free(sps);
free(pps);
return sdp;
# undef PAYLOAD
}
#define _PRE 3 // Annex B prefix length
@@ -136,22 +109,11 @@ void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame) {
}
void _rtpv_process_nalu(us_rtpv_s *rtpv, const uint8_t *data, size_t size, uint32_t pts, bool marked) {
# define DG rtpv->rtp->datagram
const unsigned ref_idc = (data[0] >> 5) & 3;
const unsigned type = data[0] & 0x1F;
us_frame_s *ps = NULL;
switch (type) {
case 7: ps = rtpv->sps; break;
case 8: ps = rtpv->pps; break;
}
if (ps != NULL) {
US_MUTEX_LOCK(rtpv->mutex);
us_frame_set_data(ps, data, size);
US_MUTEX_UNLOCK(rtpv->mutex);
}
# define DG rtpv->rtp->datagram
if (size + US_RTP_HEADER_SIZE <= US_RTP_DATAGRAM_SIZE) {
us_rtp_write_header(rtpv->rtp, pts, marked);
memcpy(DG + US_RTP_HEADER_SIZE, data, size);

View File

@@ -31,12 +31,8 @@
#include <sys/types.h>
#include <linux/videodev2.h>
#include <pthread.h>
#include "uslibs/tools.h"
#include "uslibs/threading.h"
#include "uslibs/frame.h"
#include "uslibs/base64.h"
#include "rtp.h"
@@ -44,9 +40,6 @@
typedef struct {
us_rtp_s *rtp;
us_rtp_callback_f callback;
us_frame_s *sps; // Actually not a frame, just a bytes storage
us_frame_s *pps;
pthread_mutex_t mutex;
} us_rtpv_s;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,7 +2,7 @@
set -e
[ -z "$NO_EDID" ] && {
[ -n "$EDID" ] && {
[ -n "$EDID_HEX" ] && echo "$EDID_HEX" > /edid.hex
while true; do
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
PKG_NAME:=ustreamer
PKG_VERSION:=5.24
PKG_VERSION:=5.30
PKG_RELEASE:=1
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>

View File

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

View File

@@ -181,12 +181,18 @@ static int _wait_frame(_MemsinkObject *self) {
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) {
PyErr_SetString(PyExc_RuntimeError, "Closed");
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)) {
case 0: break;
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_ts = us_get_now_monotonic();
_MEM(last_client_ts) = self->frame_ts;
if (key_required) {
_MEM(key_requested) = true;
}
if (flock(self->fd, LOCK_UN) < 0) {
return PyErr_SetFromErrno(PyExc_OSError);
@@ -260,7 +269,7 @@ static PyMethodDef _MemsinkObject_methods[] = {
ADD_METHOD("close", close, METH_NOARGS),
ADD_METHOD("__enter__", enter, METH_NOARGS),
ADD_METHOD("__exit__", exit, METH_VARARGS),
ADD_METHOD("wait_frame", wait_frame, METH_NOARGS),
ADD_METHOD("wait_frame", wait_frame, METH_VARARGS | METH_KEYWORDS),
ADD_METHOD("is_opened", is_opened, METH_NOARGS),
{},
# undef ADD_METHOD

View File

@@ -23,7 +23,7 @@ _USTR_SRCS = $(shell ls \
ustreamer/data/*.c \
ustreamer/encoders/cpu/*.c \
ustreamer/encoders/hw/*.c \
ustreamer/h264/*.c \
ustreamer/*.c \
)
_DUMP_LIBS = $(_COMMON_LIBS)

View File

@@ -52,11 +52,11 @@ void us_output_file_write(void *v_output, const us_frame_s *frame) {
us_base64_encode(frame->data, frame->used, &output->base64_data, &output->base64_allocated);
fprintf(output->fp,
"{\"size\": %zu, \"width\": %u, \"height\": %u,"
" \"format\": %u, \"stride\": %u, \"online\": %u,"
" \"format\": %u, \"stride\": %u, \"online\": %u, \"key\": %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->format, frame->stride, frame->online, frame->key,
frame->grab_ts, frame->encode_begin_ts, frame->encode_end_ts,
output->base64_data);
} else {

View File

@@ -48,6 +48,7 @@ enum _OPT_VALUES {
_O_OUTPUT_JSON = 'j',
_O_COUNT = 'c',
_O_INTERVAL = 'i',
_O_KEY_REQUIRED = 'k',
_O_HELP = 'h',
_O_VERSION = 'v',
@@ -67,6 +68,7 @@ static const struct option _LONG_OPTS[] = {
{"output-json", no_argument, NULL, _O_OUTPUT_JSON},
{"count", required_argument, NULL, _O_COUNT},
{"interval", required_argument, NULL, _O_INTERVAL},
{"key-required", no_argument, NULL, _O_KEY_REQUIRED},
{"log-level", required_argument, NULL, _O_LOG_LEVEL},
{"perf", no_argument, NULL, _O_PERF},
@@ -98,6 +100,7 @@ static void _install_signal_handlers(void);
static int _dump_sink(
const char *sink_name, unsigned sink_timeout,
long long count, long double interval,
bool key_required,
_output_context_s *ctx);
static void _help(FILE *fp);
@@ -113,6 +116,7 @@ int main(int argc, char *argv[]) {
bool output_json = false;
long long count = 0;
long double interval = 0;
bool key_required = false;
# define OPT_SET(_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_COUNT: OPT_NUMBER("--count", count, 0, LLONG_MAX, 0);
case _O_INTERVAL: OPT_LDOUBLE("--interval", interval, 0, 60);
case _O_KEY_REQUIRED: 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_PERF: OPT_SET(us_g_log_level, US_LOG_LEVEL_PERF);
@@ -186,7 +191,7 @@ int main(int argc, char *argv[]) {
}
_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) {
ctx.destroy(ctx.v_output);
}
@@ -223,6 +228,7 @@ static void _install_signal_handlers(void) {
static int _dump_sink(
const char *sink_name, unsigned sink_timeout,
long long count, long double interval,
bool key_required,
_output_context_s *ctx) {
if (count == 0) {
@@ -245,17 +251,21 @@ static int _dump_sink(
long double last_ts = 0;
while (!_g_stop) {
int error = us_memsink_client_get(sink, frame);
bool key_requested;
const int error = us_memsink_client_get(sink, frame, &key_requested, key_required);
if (error == 0) {
key_required = false;
const long double now = us_get_now_monotonic();
const long long now_second = us_floor_ms(now);
char fourcc_str[8];
US_LOG_VERBOSE("Frame: size=%zu, res=%ux%u, fourcc=%s, stride=%u, online=%d, key=%d, latency=%.3Lf, diff=%.3Lf",
frame->used, frame->width, frame->height,
US_LOG_VERBOSE("Frame: res=%ux%u, fmt=%s, stride=%u, online=%d, key=%d, kr=%d, latency=%.3Lf, diff=%.3Lf, size=%zu",
frame->width, frame->height,
us_fourcc_to_string(frame->format, fourcc_str, 8),
frame->stride, frame->online, frame->key,
now - frame->grab_ts, (last_ts ? now - last_ts : 0));
frame->stride, frame->online, frame->key, key_requested,
now - frame->grab_ts, (last_ts ? now - last_ts : 0),
frame->used);
last_ts = now;
US_LOG_DEBUG(" grab_ts=%.3Lf, encode_begin_ts=%.3Lf, encode_end_ts=%.3Lf",
@@ -322,6 +332,7 @@ static void _help(FILE *fp) {
SAY(" -j|--output-json ──────── Format output as JSON. Required option --output. Default: disabled.\n");
SAY(" -c|--count <N> ───────── Limit the number of frames. Default: 0 (infinite).\n");
SAY(" -i|--interval <sec> ───── Delay between reading frames (float). Default: 0.\n");
SAY(" -k|--key-required ─────── Request keyframe from the sink. Default: disabled.\n");
SAY("Logging options:");
SAY("════════════════");
SAY(" --log-level <N> ──── Verbosity level of messages from 0 (info) to 3 (debug).");

View File

@@ -23,7 +23,7 @@
#pragma once
#define US_VERSION_MAJOR 5
#define US_VERSION_MINOR 24
#define US_VERSION_MINOR 30
#define US_MAKE_VERSION2(_major, _minor) #_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));;
}
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);
const long double now = us_get_now_monotonic();
@@ -133,6 +133,10 @@ int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame) {
sink->last_id = us_get_now_id();
sink->mem->id = sink->last_id;
if (sink->mem->key_requested && frame->key) {
sink->mem->key_requested = false;
}
*key_requested = sink->mem->key_requested;
memcpy(sink->mem->data, frame->data, frame->used);
sink->mem->used = frame->used;
@@ -140,6 +144,7 @@ int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame) {
sink->mem->magic = US_MEMSINK_MAGIC;
sink->mem->version = US_MEMSINK_VERSION;
atomic_store(&sink->has_clients, (sink->mem->last_client_ts + sink->client_ttl > us_get_now_monotonic()));
if (flock(sink->fd, LOCK_UN) < 0) {
@@ -159,7 +164,7 @@ int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame) {
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 *const key_requested, bool key_required) { // cppcheck-suppress unusedFunction
assert(!sink->server); // Client only
if (us_flock_timedwait_monotonic(sink->fd, sink->timeout) < 0) {
@@ -182,9 +187,13 @@ int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame) { // cppcheck-s
sink->last_id = sink->mem->id;
us_frame_set_data(frame, sink->mem->data, sink->mem->used);
US_FRAME_COPY_META(sink->mem, frame);
*key_requested = sink->mem->key_requested;
retval = 0;
}
sink->mem->last_client_ts = us_get_now_monotonic();
if (key_required) {
sink->mem->key_requested = true;
}
}
done:

View File

@@ -63,6 +63,6 @@ us_memsink_s *us_memsink_init(
void us_memsink_destroy(us_memsink_s *sink);
bool us_memsink_server_check(us_memsink_s *sink, const us_frame_s *frame);
int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame);
int us_memsink_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 *const key_requested, bool key_required);

View File

@@ -30,7 +30,7 @@
#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
# define US_CFG_MEMSINK_MAX_DATA 33554432
@@ -57,6 +57,7 @@ typedef struct {
long double encode_end_ts;
long double last_client_ts;
bool key_requested;
uint8_t data[US_MEMSINK_MAX_DATA];
} us_memsink_shared_s;

View File

@@ -20,7 +20,7 @@
*****************************************************************************/
#include "stream.h"
#include "h264.h"
us_h264_stream_s *us_h264_stream_init(us_memsink_s *sink, const char *path, unsigned bitrate, unsigned gop) {
@@ -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);
}
if (h264->key_requested) {
US_LOG_INFO("H264: Requested keyframe by a sink client");
h264->key_requested = false;
force_key = true;
}
bool online = false;
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);
}

View File

@@ -26,16 +26,17 @@
#include <stdatomic.h>
#include <assert.h>
#include "../../libs/tools.h"
#include "../../libs/logging.h"
#include "../../libs/frame.h"
#include "../../libs/memsink.h"
#include "../../libs/unjpeg.h"
#include "../m2m.h"
#include "../libs/tools.h"
#include "../libs/logging.h"
#include "../libs/frame.h"
#include "../libs/memsink.h"
#include "../libs/unjpeg.h"
#include "m2m.h"
typedef struct {
us_memsink_s *sink;
bool key_requested;
us_frame_s *tmp_src;
us_frame_s *dest;
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.num_planes = 1;
// 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_XIOCTL(VIDIOC_S_FMT, &fmt, "Can't set OUTPUT format");
if (fmt.fmt.pix_mp.pixelformat != enc->output_format) {

View File

@@ -32,7 +32,8 @@ static void _stream_expose_frame(us_stream_s *stream, us_frame_s *frame, unsigne
#define _SINK_PUT(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); \
bool m_key_requested; /* Unused */ \
us_memsink_server_put(stream->x_sink, x_frame, &m_key_requested); \
} \
}

View File

@@ -42,7 +42,7 @@
#include "device.h"
#include "encoder.h"
#include "workers.h"
#include "h264/stream.h"
#include "h264.h"
#ifdef WITH_GPIO
# include "gpio/gpio.h"
#endif