Compare commits

...

16 Commits
v5.23 ... v5.25

Author SHA1 Message Date
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
Maxim Devaev
bfa1516491 Bump version: 5.23 → 5.24 2022-10-19 08:13:54 +03:00
tomaszduda23
36c9ff22b3 set HDMI EDID before starting ustreamer (#179)
* build docker image

* Push each image to an user github registry.
It can be used during development for testing.

* set HDMI EDID before starting ustreamer

* Update README.md
2022-10-18 20:11:29 +03:00
Maxim Devaev
17f54a7977 Merge pull request #178 from tomaszduda23/github_registry
GitHub registry
2022-10-18 12:08:58 +03:00
Maxim Devaev
574986d0fd added --instance-id to the manpage 2022-10-16 02:54:28 +03:00
Maxim Devaev
d5ce2e835f report version 2022-10-13 02:10:16 +03:00
Maxim Devaev
6201554ba1 instance id 2022-10-12 20:00:37 +03:00
Maxim Devaev
75dee4e91d Merge pull request #177 from tomaszduda23/master
build docker image
2022-10-09 21:09:32 +03:00
Tomasz Duda
e1b4e0db66 Push each image to an user github registry.
It can be used during development for testing.
2022-10-09 15:17:35 +02:00
Tomasz Duda
0d37b09bb4 build docker image 2022-10-09 13:28:46 +02:00
Maxim Devaev
18767b68ff Merge pull request #176 from tomaszduda23/master
check compilation
2022-10-03 16:47:05 +03:00
Tomasz Duda
7975615c6c check compilation 2022-10-03 02:31:31 +02:00
18 changed files with 246 additions and 17 deletions

View File

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

View File

@@ -7,3 +7,4 @@
!python/**
!janus/**
!man/**
!pkg/docker/entry.sh

View File

@@ -0,0 +1,81 @@
name: Build Alpine
on:
push:
branches: [master, build_*]
tags: [v*]
pull_request:
branches: [master]
jobs:
buildx:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v3
-
name: Login to GitHub Container Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
-
name: Login to DockerHub Container Registry
if: startsWith(github.ref, 'refs/tags/v')
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: |
${{ secrets.DOCKERHUB_USERNAME }}/ustreamer,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
ghcr.io/${{ github.repository }},enable=${{ github.event_name != 'pull_request' }}
tags: |
type=ref,event=tag
type=sha,format=long
-
name: Set up QEMU
uses: docker/setup-qemu-action@v2
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
with:
driver-opts: image=moby/buildkit:master
-
name: Build and export to Docker
uses: docker/build-push-action@v3
with:
context: .
load: true
tags: ustreamer
file: pkg/docker/Dockerfile.alpine
-
name: Test
run: |
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
with:
context: .
platforms: linux/arm64,linux/amd64,linux/arm/v7
file: pkg/docker/Dockerfile.alpine
-
name: Push
if: steps.meta.outputs.tags != ''
uses: docker/build-push-action@v3
with:
context: .
platforms: linux/arm64,linux/amd64,linux/arm/v7
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
file: pkg/docker/Dockerfile.alpine

View File

@@ -81,8 +81,57 @@ $ ./ustreamer \
You can always view the full list of options with ```ustreamer --help```.
-----
# Docker (Raspberry Pi 4 HDMI)
## 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 -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
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

@@ -250,7 +250,7 @@ static int _plugin_init(janus_callbacks *gw, const char *config_dir_path) {
// sysctl -w net.core.rmem_max=1000000
// sysctl -w net.core.wmem_max=1000000
US_JLOG_INFO("main", "Initializing plugin ...");
US_JLOG_INFO("main", "Initializing PiKVM uStreamer plugin %s ...", US_VERSION);
if (gw == NULL || config_dir_path == NULL || ((_g_config = us_config_init(config_dir_path)) == NULL)) {
return -1;
}

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.23" "January 2021"
.TH USTREAMER-DUMP 1 "version 5.25" "January 2021"
.SH NAME
ustreamer-dump \- Dump uStreamer's memory sink to file

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.23" "November 2020"
.TH USTREAMER 1 "version 5.25" "November 2020"
.SH NAME
ustreamer \- stream MJPEG video from any V4L2 device to the network
@@ -203,6 +203,9 @@ Default: disabled.
.BR \-\-allow\-origin\ \fIstr
Set Access\-Control\-Allow\-Origin header. Default: disabled.
.TP
.BR \-\-instance\-id\ \fIstr
A short string identifier to be displayed in the /state handle. It must satisfy regexp ^[a-zA-Z0-9\./+_-]*$. Default: an empty string.
.TP
.BR \-\-server\-timeout\ \fIsec
Timeout for client connections. Default: 10.

View File

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

View File

@@ -0,0 +1,34 @@
FROM alpine:3.16 as build
RUN apk add --no-cache \
alpine-sdk \
linux-headers \
libjpeg-turbo-dev \
libevent-dev \
libbsd-dev \
libgpiod-dev
WORKDIR /build/ustreamer/
COPY . .
RUN make -j5 WITH_GPIO=1
FROM alpine:3.16 as run
RUN apk add --no-cache \
libevent \
libjpeg-turbo \
libevent \
libgpiod \
libbsd \
v4l-utils
WORKDIR /ustreamer
COPY --from=build /build/ustreamer/src/ustreamer.bin ustreamer
RUN wget https://raw.githubusercontent.com/pikvm/kvmd/master/configs/kvmd/tc358743-edid.hex -O /edid.hex
COPY pkg/docker/entry.sh /
EXPOSE 8080
ENTRYPOINT ["/entry.sh"]
CMD ["--dv-timings", "--format", "UYVY"]
# vim: syntax=dockerfile

14
pkg/docker/entry.sh Executable file
View File

@@ -0,0 +1,14 @@
#!/bin/sh
set -e
[ -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
echo 'Failed to set EDID. Reetrying...'
sleep 1
done
}
./ustreamer --host=0.0.0.0 $@

View File

@@ -6,7 +6,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=ustreamer
PKG_VERSION:=5.23
PKG_VERSION:=5.25
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.23",
version="5.25",
description="uStreamer tools",
author="Maxim Devaev",
author_email="mdevaev@gmail.com",

View File

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

View File

@@ -75,6 +75,7 @@ us_server_s *us_server_init(us_stream_s *stream) {
server->passwd = "";
server->static_path = "";
server->allow_origin = "";
server->instance_id = "";
server->timeout = 10;
server->run = run;
@@ -400,7 +401,9 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
_A_EVBUFFER_ADD_PRINTF(buf,
"{\"ok\": true, \"result\": {"
" \"instance_id\": \"%s\","
" \"encoder\": {\"type\": \"%s\", \"quality\": %u},",
server->instance_id,
us_encoder_type_to_string(enc_type),
enc_quality
);

View File

@@ -149,6 +149,7 @@ typedef struct us_server_sx {
char *passwd;
char *static_path;
char *allow_origin;
char *instance_id;
unsigned drop_same_frames;
unsigned fake_width;

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

@@ -82,6 +82,7 @@ enum _US_OPT_VALUES {
_O_PASSWD,
_O_STATIC,
_O_ALLOW_ORIGIN,
_O_INSTANCE_ID,
_O_TCP_NODELAY,
_O_SERVER_TIMEOUT,
@@ -177,6 +178,7 @@ static const struct option _LONG_OPTS[] = {
{"static", required_argument, NULL, _O_STATIC},
{"drop-same-frames", required_argument, NULL, _O_DROP_SAME_FRAMES},
{"allow-origin", required_argument, NULL, _O_ALLOW_ORIGIN},
{"instance-id", required_argument, NULL, _O_INSTANCE_ID},
{"fake-resolution", required_argument, NULL, _O_FAKE_RESOLUTION},
{"tcp-nodelay", no_argument, NULL, _O_TCP_NODELAY},
{"server-timeout", required_argument, NULL, _O_SERVER_TIMEOUT},
@@ -228,6 +230,7 @@ static const struct option _LONG_OPTS[] = {
static int _parse_resolution(const char *str, unsigned *width, unsigned *height, bool limited);
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);
@@ -419,6 +422,13 @@ int options_parse(us_options_s *options, us_device_s *dev, us_encoder_s *enc, us
case _O_DROP_SAME_FRAMES: OPT_NUMBER("--drop-same-frames", server->drop_same_frames, 0, US_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_INSTANCE_ID:
if (_check_instance_id(optarg) != 0) {
printf("Invalid instance ID, it should be like: ^[a-zA-Z0-9\\./+_-]*$\n");
return -1;
}
server->instance_id = optarg;
break;
case _O_TCP_NODELAY: OPT_SET(server->tcp_nodelay, true);
case _O_SERVER_TIMEOUT: OPT_NUMBER("--server-timeout", server->timeout, 1, 60, 0);
@@ -473,6 +483,8 @@ int options_parse(us_options_s *options, us_device_s *dev, us_encoder_s *enc, us
}
}
US_LOG_INFO("Starting PiKVM uStreamer %s ...", US_VERSION);
options->blank = us_blank_frame_init(blank_path);
stream->blank = options->blank;
@@ -530,6 +542,18 @@ static int _parse_resolution(const char *str, unsigned *width, unsigned *height,
return 0;
}
static int _check_instance_id(const char *str) {
for (const char *ptr = str; *ptr; ++ptr) {
if (!(isascii(*ptr) && (
isalpha(*ptr) || isdigit(*ptr)
|| *ptr == '.' || *ptr == '/' || *ptr == '+' || *ptr == '_' || *ptr == '-'
))) {
return -1;
}
}
return 0;
}
static void _features(void) {
# ifdef WITH_GPIO
puts("+ WITH_GPIO");
@@ -657,6 +681,8 @@ static void _help(FILE *fp, us_device_s *dev, us_encoder_s *enc, us_stream_s *st
SAY(" --tcp-nodelay ────────────── Set TCP_NODELAY flag to the client /stream socket. Only for TCP socket.");
SAY(" Default: disabled.\n");
SAY(" --allow-origin <str> ─────── Set Access-Control-Allow-Origin header. Default: disabled.\n");
SAY(" --instance-id <str> ──────── A short string identifier to be displayed in the /state handle.");
SAY(" It must satisfy regexp ^[a-zA-Z0-9\\./+_-]*$. Default: an empty string.\n");
SAY(" --server-timeout <sec> ───── Timeout for client connections. Default: %u.\n", server->timeout);
# define ADD_SINK(x_name, x_opt) \
SAY(x_name " sink options:"); \