mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-27 12:16:31 +00:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40be0c20e2 | ||
|
|
f1e9d4568c | ||
|
|
f4f57cce38 | ||
|
|
c87ad5703c | ||
|
|
95df13b7cb | ||
|
|
bfa1516491 | ||
|
|
36c9ff22b3 | ||
|
|
17f54a7977 | ||
|
|
574986d0fd | ||
|
|
d5ce2e835f | ||
|
|
6201554ba1 | ||
|
|
75dee4e91d | ||
|
|
e1b4e0db66 | ||
|
|
0d37b09bb4 | ||
|
|
18767b68ff | ||
|
|
7975615c6c |
@@ -1,7 +1,7 @@
|
|||||||
[bumpversion]
|
[bumpversion]
|
||||||
commit = True
|
commit = True
|
||||||
tag = True
|
tag = True
|
||||||
current_version = 5.23
|
current_version = 5.25
|
||||||
parse = (?P<major>\d+)\.(?P<minor>\d+)
|
parse = (?P<major>\d+)\.(?P<minor>\d+)
|
||||||
serialize =
|
serialize =
|
||||||
{major}.{minor}
|
{major}.{minor}
|
||||||
|
|||||||
@@ -7,3 +7,4 @@
|
|||||||
!python/**
|
!python/**
|
||||||
!janus/**
|
!janus/**
|
||||||
!man/**
|
!man/**
|
||||||
|
!pkg/docker/entry.sh
|
||||||
|
|||||||
81
.github/workflows/docker-alpine-image.yaml
vendored
Normal file
81
.github/workflows/docker-alpine-image.yaml
vendored
Normal 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
|
||||||
49
README.md
49
README.md
@@ -81,8 +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)
|
||||||
|
|
||||||
|
## 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
|
# 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
|
||||||
|
|||||||
29
docs/h264.md
29
docs/h264.md
@@ -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());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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.rmem_max=1000000
|
||||||
// sysctl -w net.core.wmem_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)) {
|
if (gw == NULL || config_dir_path == NULL || ((_g_config = us_config_init(config_dir_path)) == NULL)) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.23" "January 2021"
|
.TH USTREAMER-DUMP 1 "version 5.25" "January 2021"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
ustreamer-dump \- Dump uStreamer's memory sink to file
|
ustreamer-dump \- Dump uStreamer's memory sink to 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.23" "November 2020"
|
.TH USTREAMER 1 "version 5.25" "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
|
||||||
@@ -203,6 +203,9 @@ Default: disabled.
|
|||||||
.BR \-\-allow\-origin\ \fIstr
|
.BR \-\-allow\-origin\ \fIstr
|
||||||
Set Access\-Control\-Allow\-Origin header. Default: disabled.
|
Set Access\-Control\-Allow\-Origin header. Default: disabled.
|
||||||
.TP
|
.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
|
.BR \-\-server\-timeout\ \fIsec
|
||||||
Timeout for client connections. Default: 10.
|
Timeout for client connections. Default: 10.
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
|
|
||||||
pkgname=ustreamer
|
pkgname=ustreamer
|
||||||
pkgver=5.23
|
pkgver=5.25
|
||||||
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"
|
||||||
|
|||||||
34
pkg/docker/Dockerfile.alpine
Normal file
34
pkg/docker/Dockerfile.alpine
Normal 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
14
pkg/docker/entry.sh
Executable 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 $@
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=ustreamer
|
PKG_NAME:=ustreamer
|
||||||
PKG_VERSION:=5.23
|
PKG_VERSION:=5.25
|
||||||
PKG_RELEASE:=1
|
PKG_RELEASE:=1
|
||||||
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
||||||
|
|
||||||
|
|||||||
@@ -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.23",
|
version="5.25",
|
||||||
description="uStreamer tools",
|
description="uStreamer tools",
|
||||||
author="Maxim Devaev",
|
author="Maxim Devaev",
|
||||||
author_email="mdevaev@gmail.com",
|
author_email="mdevaev@gmail.com",
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define US_VERSION_MAJOR 5
|
#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_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)
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ us_server_s *us_server_init(us_stream_s *stream) {
|
|||||||
server->passwd = "";
|
server->passwd = "";
|
||||||
server->static_path = "";
|
server->static_path = "";
|
||||||
server->allow_origin = "";
|
server->allow_origin = "";
|
||||||
|
server->instance_id = "";
|
||||||
server->timeout = 10;
|
server->timeout = 10;
|
||||||
server->run = run;
|
server->run = run;
|
||||||
|
|
||||||
@@ -400,7 +401,9 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
|
|||||||
|
|
||||||
_A_EVBUFFER_ADD_PRINTF(buf,
|
_A_EVBUFFER_ADD_PRINTF(buf,
|
||||||
"{\"ok\": true, \"result\": {"
|
"{\"ok\": true, \"result\": {"
|
||||||
|
" \"instance_id\": \"%s\","
|
||||||
" \"encoder\": {\"type\": \"%s\", \"quality\": %u},",
|
" \"encoder\": {\"type\": \"%s\", \"quality\": %u},",
|
||||||
|
server->instance_id,
|
||||||
us_encoder_type_to_string(enc_type),
|
us_encoder_type_to_string(enc_type),
|
||||||
enc_quality
|
enc_quality
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -149,6 +149,7 @@ typedef struct us_server_sx {
|
|||||||
char *passwd;
|
char *passwd;
|
||||||
char *static_path;
|
char *static_path;
|
||||||
char *allow_origin;
|
char *allow_origin;
|
||||||
|
char *instance_id;
|
||||||
|
|
||||||
unsigned drop_same_frames;
|
unsigned drop_same_frames;
|
||||||
unsigned fake_width;
|
unsigned fake_width;
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ enum _US_OPT_VALUES {
|
|||||||
_O_PASSWD,
|
_O_PASSWD,
|
||||||
_O_STATIC,
|
_O_STATIC,
|
||||||
_O_ALLOW_ORIGIN,
|
_O_ALLOW_ORIGIN,
|
||||||
|
_O_INSTANCE_ID,
|
||||||
_O_TCP_NODELAY,
|
_O_TCP_NODELAY,
|
||||||
_O_SERVER_TIMEOUT,
|
_O_SERVER_TIMEOUT,
|
||||||
|
|
||||||
@@ -177,6 +178,7 @@ static const struct option _LONG_OPTS[] = {
|
|||||||
{"static", required_argument, NULL, _O_STATIC},
|
{"static", required_argument, NULL, _O_STATIC},
|
||||||
{"drop-same-frames", required_argument, NULL, _O_DROP_SAME_FRAMES},
|
{"drop-same-frames", required_argument, NULL, _O_DROP_SAME_FRAMES},
|
||||||
{"allow-origin", required_argument, NULL, _O_ALLOW_ORIGIN},
|
{"allow-origin", required_argument, NULL, _O_ALLOW_ORIGIN},
|
||||||
|
{"instance-id", required_argument, NULL, _O_INSTANCE_ID},
|
||||||
{"fake-resolution", required_argument, NULL, _O_FAKE_RESOLUTION},
|
{"fake-resolution", required_argument, NULL, _O_FAKE_RESOLUTION},
|
||||||
{"tcp-nodelay", no_argument, NULL, _O_TCP_NODELAY},
|
{"tcp-nodelay", no_argument, NULL, _O_TCP_NODELAY},
|
||||||
{"server-timeout", required_argument, NULL, _O_SERVER_TIMEOUT},
|
{"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 _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 _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, 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_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_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_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_TCP_NODELAY: OPT_SET(server->tcp_nodelay, true);
|
||||||
case _O_SERVER_TIMEOUT: OPT_NUMBER("--server-timeout", server->timeout, 1, 60, 0);
|
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);
|
options->blank = us_blank_frame_init(blank_path);
|
||||||
stream->blank = options->blank;
|
stream->blank = options->blank;
|
||||||
|
|
||||||
@@ -530,6 +542,18 @@ static int _parse_resolution(const char *str, unsigned *width, unsigned *height,
|
|||||||
return 0;
|
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) {
|
static void _features(void) {
|
||||||
# ifdef WITH_GPIO
|
# ifdef WITH_GPIO
|
||||||
puts("+ 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(" --tcp-nodelay ────────────── Set TCP_NODELAY flag to the client /stream socket. Only for TCP socket.");
|
||||||
SAY(" Default: disabled.\n");
|
SAY(" Default: disabled.\n");
|
||||||
SAY(" --allow-origin <str> ─────── Set Access-Control-Allow-Origin header. 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);
|
SAY(" --server-timeout <sec> ───── Timeout for client connections. Default: %u.\n", server->timeout);
|
||||||
# define ADD_SINK(x_name, x_opt) \
|
# define ADD_SINK(x_name, x_opt) \
|
||||||
SAY(x_name " sink options:"); \
|
SAY(x_name " sink options:"); \
|
||||||
|
|||||||
Reference in New Issue
Block a user