mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-26 19:56:33 +00:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d4e9fbb1a | ||
|
|
4a59b038ec | ||
|
|
6795744aea | ||
|
|
798fe04905 | ||
|
|
1460de95c1 | ||
|
|
358950d0c2 | ||
|
|
caef82d96f | ||
|
|
dff7dd7087 | ||
|
|
79bb881a34 | ||
|
|
f0763e3865 | ||
|
|
a8dfa96db0 | ||
|
|
7ceb8a3da5 | ||
|
|
9344059e0e | ||
|
|
ee2b1afe5b | ||
|
|
31f47f2eac | ||
|
|
69a9bafcd5 | ||
|
|
dbecdf5d9b | ||
|
|
967574a78a | ||
|
|
bb4f6f3993 |
@@ -1,7 +1,7 @@
|
|||||||
[bumpversion]
|
[bumpversion]
|
||||||
commit = True
|
commit = True
|
||||||
tag = True
|
tag = True
|
||||||
current_version = 5.0
|
current_version = 5.4
|
||||||
parse = (?P<major>\d+)\.(?P<minor>\d+)
|
parse = (?P<major>\d+)\.(?P<minor>\d+)
|
||||||
serialize =
|
serialize =
|
||||||
{major}.{minor}
|
{major}.{minor}
|
||||||
|
|||||||
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
|
|
||||||
patreon: pikvm
|
patreon: pikvm
|
||||||
custom: https://www.paypal.me/mdevaev
|
#custom: https://www.paypal.me/mdevaev
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ You'll need ```make```, ```gcc```, ```libevent``` with ```pthreads``` support,
|
|||||||
* Arch: `sudo pacman -S libevent libjpeg-turbo libutil-linux libbsd`.
|
* 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`.
|
||||||
* Debian/Ubuntu: `sudo apt install build-essential libevent-dev libjpeg-dev libbsd-dev`.
|
* 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`.
|
||||||
|
|
||||||
To enable GPIO support install [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) and pass option ```WITH_GPIO=1```. If the compiler reports about a missing function ```pthread_get_name_np()``` (or similar), add option ```WITH_PTHREAD_NP=0``` (it's enabled by default). For the similar error with ```setproctitle()``` add option ```WITH_SETPROCTITLE=0```.
|
To enable GPIO support install [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) and pass option ```WITH_GPIO=1```. If the compiler reports about a missing function ```pthread_get_name_np()``` (or similar), add option ```WITH_PTHREAD_NP=0``` (it's enabled by default). For the similar error with ```setproctitle()``` add option ```WITH_SETPROCTITLE=0```.
|
||||||
|
|
||||||
@@ -56,6 +57,8 @@ FreeBSD port: https://www.freshports.org/multimedia/ustreamer.
|
|||||||
|
|
||||||
-----
|
-----
|
||||||
# Usage
|
# Usage
|
||||||
|
**For M2M hardware encoding on Raspberry Pi, you need at least 5.15.32 kernel. OpenMAX and MMAL support on older kernels is deprecated and removed.**
|
||||||
|
|
||||||
Without arguments, ```ustreamer``` will try to open ```/dev/video0``` with 640x480 resolution and start streaming on ```http://127.0.0.1:8080```. You can override this behavior using parameters ```--device```, ```--host``` and ```--port```. For example, to stream to the world, run:
|
Without arguments, ```ustreamer``` will try to open ```/dev/video0``` with 640x480 resolution and start streaming on ```http://127.0.0.1:8080```. You can override this behavior using parameters ```--device```, ```--host``` and ```--port```. For example, to stream to the world, run:
|
||||||
```
|
```
|
||||||
# ./ustreamer --device=/dev/video1 --host=0.0.0.0 --port=80
|
# ./ustreamer --device=/dev/video1 --host=0.0.0.0 --port=80
|
||||||
@@ -97,6 +100,9 @@ $ modprobe bcm2835-v4l2 max_video_width=2592 max_video_height=1944
|
|||||||
-----
|
-----
|
||||||
# Integrations
|
# Integrations
|
||||||
|
|
||||||
|
## Janus
|
||||||
|
µStreamer supports bandwidth-efficient streaming using [H.264 compression](https://en.wikipedia.org/wiki/Advanced_Video_Coding) and the Janus WebRTC server. See the [Janus integration guide](docs/h264.md) for full details.
|
||||||
|
|
||||||
## Nginx
|
## Nginx
|
||||||
When uStreamer is behind an Nginx proxy, it's buffering behavior introduces latency into the video stream. It's possible to disable Nginx's buffering to eliminate the additional latency:
|
When uStreamer is behind an Nginx proxy, it's buffering behavior introduces latency into the video stream. It's possible to disable Nginx's buffering to eliminate the additional latency:
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ $ ./ustreamer --help
|
|||||||
|
|
||||||
-----
|
-----
|
||||||
# Использование
|
# Использование
|
||||||
|
**Для аппаратного кодирования M2M на Raspberry Pi, вам нужно ядро минимальной версии 5.15.32. Поддержка OpenMAX и MMAL для более старых ядер объявлена устаревшей и была удалена.**
|
||||||
|
|
||||||
Будучи запущенным без аргументов, ```ustreamer``` попробует открыть устройство ```/dev/video0``` с разрешением 640x480 и начать трансляцию на ```http://127.0.0.1:8080```. Это поведение может быть изменено с помощью опций ```--device```, ```--host``` и ```--port```. Пример вещания на всю сеть по 80-м порту:
|
Будучи запущенным без аргументов, ```ustreamer``` попробует открыть устройство ```/dev/video0``` с разрешением 640x480 и начать трансляцию на ```http://127.0.0.1:8080```. Это поведение может быть изменено с помощью опций ```--device```, ```--host``` и ```--port```. Пример вещания на всю сеть по 80-м порту:
|
||||||
```
|
```
|
||||||
# ./ustreamer --device=/dev/video1 --host=0.0.0.0 --port=80
|
# ./ustreamer --device=/dev/video1 --host=0.0.0.0 --port=80
|
||||||
|
|||||||
231
docs/h264.md
Normal file
231
docs/h264.md
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
# Streaming H.264 Video over WebRTC with Janus
|
||||||
|
|
||||||
|
µStreamer supports bandwidth-efficient streaming using [H.264 compression](https://en.wikipedia.org/wiki/Advanced_Video_Coding) and the Janus WebRTC server.
|
||||||
|
|
||||||
|
This guide explains how to configure µStreamer to provide an H.264 video stream and consume it within a web application using WebRTC.
|
||||||
|
|
||||||
|
## Components
|
||||||
|
|
||||||
|
In addition to µStreamer, H.264 streaming involves the following components:
|
||||||
|
|
||||||
|
- [**Janus**](https://janus.conf.meetecho.com/): A general-purpose WebRTC server.
|
||||||
|
- [**µStreamer Janus Plugin**](https://github.com/pikvm/ustreamer/tree/master/janus): A Janus plugin that allows Janus to consume a video stream from µStreamer via shared memory.
|
||||||
|
- [**Janus JavaScript Client**](https://janus.conf.meetecho.com/docs/JS.html): A frontend library that communicates with the Janus server and the µStreamer Janus plugin.
|
||||||
|
|
||||||
|
## Control Flow
|
||||||
|
|
||||||
|
To connect to a µStreamer video stream over WebRTC, an application must establish the following control flow:
|
||||||
|
|
||||||
|
1. The backend server starts the µStreamer service.
|
||||||
|
1. The backend server starts the Janus WebRTC server with the µStreamer Janus plugin.
|
||||||
|
1. The client-side JavaScript application establishes a connection to the Janus server.
|
||||||
|
1. The client-side JavaScript application instructs the Janus server to attach the µStreamer Janus plugin and start the video stream.
|
||||||
|
1. The client-side JavaScript application renders the video stream in the web browser.
|
||||||
|
|
||||||
|
## Server Setup
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
To integrate with Janus, µStreamer has the following prerequisites:
|
||||||
|
|
||||||
|
- The system packages that µStreamer depends on (see the [main build instructions](../README.md#building)).
|
||||||
|
- The Janus WebRTC server with WebSockets transport enabled (see the [Janus documentation](https://github.com/meetecho/janus-gateway)).
|
||||||
|
|
||||||
|
### Fixing Janus C Headers
|
||||||
|
|
||||||
|
To compile µStreamer with the Janus option, (see [“Installation”](#installation)), you need to make the Janus header files available within µStreamer's build context.
|
||||||
|
|
||||||
|
First, create a symbolic link from `/usr/include` to the Janus install directory. By default, Janus installs to `/opt/janus`.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ln -s /opt/janus/include/janus /usr/include/janus
|
||||||
|
```
|
||||||
|
|
||||||
|
Janus uses `#include` paths that make it difficult for third-party libraries to build against its headers. To fix this, modify the `#include` directive in `janus/plugins/plugin.h` to prepend a `../` to the included file name (`#include "refcount.h"` → `#include "../refcount.h"`).
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sed \
|
||||||
|
--in-place \
|
||||||
|
--expression 's|^#include "refcount.h"$|#include "../refcount.h"|g' \
|
||||||
|
/usr/include/janus/plugins/plugin.h
|
||||||
|
```
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
First, compile µStreamer with the Janus plugin option (`WITH_JANUS`).
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone --depth=1 https://github.com/pikvm/ustreamer
|
||||||
|
cd ustreamer
|
||||||
|
make WITH_JANUS=1
|
||||||
|
```
|
||||||
|
|
||||||
|
The `WITH_JANUS` option compiles the µStreamer Janus plugin and outputs it to `janus/libjanus_ustreamer.so`. Move this file to the plugin directory of your Janus installation.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mv \
|
||||||
|
janus/libjanus_ustreamer.so \
|
||||||
|
/opt/janus/lib/janus/plugins/libjanus_ustreamer.so
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, specify a qualifier for the shared memory object so that the µStreamer Janus plugin can read µStreamer's video data.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cat > /opt/janus/lib/janus/configs/janus.plugin.ustreamer.jcfg <<EOF
|
||||||
|
memsink: {
|
||||||
|
object = "demo::ustreamer::h264"
|
||||||
|
}
|
||||||
|
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:
|
||||||
|
|
||||||
|
- `--h264-sink` with the qualifier of the shared memory object you specified above (`demo::ustreamer::h264`)
|
||||||
|
- `--h264-sink-mode` with the permissions bitmask for the shared memory object (e.g., `660`)
|
||||||
|
- `--h264-sink-rm` to clean up the shared memory object when the µStreamer process exits
|
||||||
|
|
||||||
|
To load the µStreamer Janus plugin and configuration, the Janus WebRTC server must run with the following command-line flags:
|
||||||
|
|
||||||
|
- `--configs-folder` with the path to the Janus configuration directory (e.g., `/opt/janus/lib/janus/configs/`)
|
||||||
|
- `--plugins-folder` with the path to the Janus plugin directory (e.g., `/opt/janus/lib/janus/plugins/`)
|
||||||
|
|
||||||
|
## Client Setup
|
||||||
|
|
||||||
|
Once an application's backend server is running µStreamer and Janus, a browser-based JavaScript application can consume the server's video stream.
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
The client needs to load the following JavaScript libraries:
|
||||||
|
|
||||||
|
- [WebRTC Adapter library](https://webrtc.github.io/adapter/adapter-8.1.0.js) (`webrtc-adapter.js`, version `8.1.0`)
|
||||||
|
- [Janus Gateway JavaScript library](https://raw.githubusercontent.com/meetecho/janus-gateway/v1.0.0/html/janus.js) (`janus.js`, version `1.0.0`)
|
||||||
|
|
||||||
|
### Control Flow
|
||||||
|
|
||||||
|
The client-side JavaScript application uses the following control flow:
|
||||||
|
|
||||||
|
1. The client loads and initializes the Janus client library.
|
||||||
|
1. The client establishes a WebSockets connection to the Janus server.
|
||||||
|
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
|
||||||
|
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.
|
||||||
|
|
||||||
|
### Sample Code
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>µStreamer H.264 demo</title>
|
||||||
|
<script src="https://webrtc.github.io/adapter/adapter-8.1.0.js"></script>
|
||||||
|
<!-- janus.js is the JavaScript client library of Janus, as specified above in
|
||||||
|
the prerequisites section of the client setup. You might need to change
|
||||||
|
the `src` path, depending on where you serve this file from. -->
|
||||||
|
<script src="janus.js"></script>
|
||||||
|
<style>
|
||||||
|
video {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<video id="webrtc-output" autoplay playsinline muted></video>
|
||||||
|
<script type="text/javascript">
|
||||||
|
// Initialize Janus library.
|
||||||
|
Janus.init({
|
||||||
|
// Turn on debug logs in the browser console.
|
||||||
|
debug: true,
|
||||||
|
|
||||||
|
// Configure Janus to use standard browser APIs internally.
|
||||||
|
dependencies: Janus.useDefaultDependencies(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Establish a WebSockets connection to the server.
|
||||||
|
const janus = new Janus({
|
||||||
|
// Specify the URL of the Janus server’s WebSockets endpoint.
|
||||||
|
server: `ws://${window.location.hostname}:8188/`,
|
||||||
|
|
||||||
|
// Callback function if the client connects successfully.
|
||||||
|
success: attachUStreamerPlugin,
|
||||||
|
|
||||||
|
// Callback function if the client fails to connect.
|
||||||
|
error: console.error,
|
||||||
|
});
|
||||||
|
|
||||||
|
let uStreamerPluginHandle = null;
|
||||||
|
|
||||||
|
function attachUStreamerPlugin() {
|
||||||
|
// Instruct the server to attach the µStreamer Janus plugin.
|
||||||
|
janus.attach({
|
||||||
|
// Qualifier of the plugin.
|
||||||
|
plugin: "janus.plugin.ustreamer",
|
||||||
|
|
||||||
|
// Callback function, for when the server attached the plugin
|
||||||
|
// successfully.
|
||||||
|
success: function (pluginHandle) {
|
||||||
|
uStreamerPluginHandle = pluginHandle;
|
||||||
|
// Instruct the µStreamer Janus plugin to initiate the video stream.
|
||||||
|
uStreamerPluginHandle.send({ message: { request: "watch" } });
|
||||||
|
},
|
||||||
|
|
||||||
|
// Callback function if the server fails to attach the plugin.
|
||||||
|
error: console.error,
|
||||||
|
|
||||||
|
// Callback function for processing messages from the Janus server.
|
||||||
|
onmessage: function (msg, jsepOffer) {
|
||||||
|
// 503 indicates that the plugin is not ready to stream yet. Retry the
|
||||||
|
// watch request until the video stream is available.
|
||||||
|
if (msg.error_code === 503) {
|
||||||
|
uStreamerPluginHandle.send({ message: { request: "watch" } });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is a JSEP offer, respond to it. This starts the WebRTC
|
||||||
|
// connection.
|
||||||
|
if (jsepOffer) {
|
||||||
|
uStreamerPluginHandle.createAnswer({
|
||||||
|
jsep: jsepOffer,
|
||||||
|
// Prevent the client from sending audio and video, as this would
|
||||||
|
// trigger a permission dialog in the browser.
|
||||||
|
media: { audioSend: false, videoSend: false },
|
||||||
|
success: function (jsepAnswer) {
|
||||||
|
uStreamerPluginHandle.send({
|
||||||
|
message: { request: "start" },
|
||||||
|
jsep: jsepAnswer,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: console.error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Callback function, for when the video 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;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
@@ -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.0" "January 2021"
|
.TH USTREAMER-DUMP 1 "version 5.4" "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.0" "November 2020"
|
.TH USTREAMER 1 "version 5.4" "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
|
||||||
|
|||||||
@@ -3,19 +3,19 @@
|
|||||||
|
|
||||||
|
|
||||||
pkgname=ustreamer
|
pkgname=ustreamer
|
||||||
pkgver=5.0
|
pkgver=5.4
|
||||||
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"
|
||||||
license=(GPL)
|
license=(GPL)
|
||||||
arch=(i686 x86_64 armv6h armv7h aarch64)
|
arch=(i686 x86_64 armv6h armv7h aarch64)
|
||||||
depends=(libjpeg libevent libbsd libgpiod)
|
depends=(libjpeg libevent libbsd libgpiod systemd)
|
||||||
makedepends=(gcc make)
|
makedepends=(gcc make systemd)
|
||||||
source=(${pkgname}::"git+https://github.com/pikvm/ustreamer#commit=v${pkgver}")
|
source=(${pkgname}::"git+https://github.com/pikvm/ustreamer#commit=v${pkgver}")
|
||||||
md5sums=(SKIP)
|
md5sums=(SKIP)
|
||||||
|
|
||||||
|
|
||||||
_options="WITH_GPIO=1"
|
_options="WITH_GPIO=1 WITH_SYSTEMD=1"
|
||||||
if [ -e /usr/bin/python3 ]; then
|
if [ -e /usr/bin/python3 ]; then
|
||||||
_options="$_options WITH_PYTHON=1"
|
_options="$_options WITH_PYTHON=1"
|
||||||
depends+=(python)
|
depends+=(python)
|
||||||
@@ -26,11 +26,6 @@ if [ -e /usr/include/janus/plugins/plugin.h ];then
|
|||||||
makedepends+=(janus-gateway-pikvm)
|
makedepends+=(janus-gateway-pikvm)
|
||||||
_options="$_options WITH_JANUS=1"
|
_options="$_options WITH_JANUS=1"
|
||||||
fi
|
fi
|
||||||
if [ -e /usr/include/systemd/sd-daemon.h ];then
|
|
||||||
depends+=(systemd)
|
|
||||||
makedepends+=(systemd)
|
|
||||||
_options="$_options WITH_SYSTEMD=1"
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
# LD does not link mmal with this option
|
# LD does not link mmal with this option
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=ustreamer
|
PKG_NAME:=ustreamer
|
||||||
PKG_VERSION:=5.0
|
PKG_VERSION:=5.4
|
||||||
PKG_RELEASE:=1
|
PKG_RELEASE:=1
|
||||||
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from setuptools import setup
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
setup(
|
setup(
|
||||||
name="ustreamer",
|
name="ustreamer",
|
||||||
version="5.0",
|
version="5.4",
|
||||||
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 VERSION_MAJOR 5
|
#define VERSION_MAJOR 5
|
||||||
#define VERSION_MINOR 0
|
#define VERSION_MINOR 4
|
||||||
|
|
||||||
#define MAKE_VERSION2(_major, _minor) #_major "." #_minor
|
#define MAKE_VERSION2(_major, _minor) #_major "." #_minor
|
||||||
#define MAKE_VERSION1(_major, _minor) MAKE_VERSION2(_major, _minor)
|
#define MAKE_VERSION1(_major, _minor) MAKE_VERSION2(_major, _minor)
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <limits.h>
|
||||||
#include <locale.h>
|
#include <locale.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
@@ -37,6 +38,15 @@
|
|||||||
#include <sys/file.h>
|
#include <sys/file.h>
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef NDEBUG
|
||||||
|
# error WTF dude? Asserts are good things!
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if CHAR_BIT != 8
|
||||||
|
# error There are not 8 bits in a char!
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
#define RN "\r\n"
|
#define RN "\r\n"
|
||||||
|
|
||||||
#define INLINE inline __attribute__((always_inline))
|
#define INLINE inline __attribute__((always_inline))
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ server_s *server_init(stream_s *stream) {
|
|||||||
assert(!evthread_use_pthreads());
|
assert(!evthread_use_pthreads());
|
||||||
assert((run->base = event_base_new()));
|
assert((run->base = event_base_new()));
|
||||||
assert((run->http = evhttp_new(run->base)));
|
assert((run->http = evhttp_new(run->base)));
|
||||||
evhttp_set_allowed_methods(run->http, EVHTTP_REQ_GET|EVHTTP_REQ_HEAD);
|
evhttp_set_allowed_methods(run->http, EVHTTP_REQ_GET|EVHTTP_REQ_HEAD|EVHTTP_REQ_OPTIONS);
|
||||||
return server;
|
return server;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,14 +212,36 @@ void server_loop_break(server_s *server) {
|
|||||||
event_base_loopbreak(RUN(base));
|
event_base_loopbreak(RUN(base));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define GET_HEADER(_key) \
|
||||||
|
evhttp_find_header(evhttp_request_get_input_headers(request), _key)
|
||||||
|
|
||||||
#define ADD_HEADER(_key, _value) \
|
#define ADD_HEADER(_key, _value) \
|
||||||
assert(!evhttp_add_header(evhttp_request_get_output_headers(request), _key, _value))
|
assert(!evhttp_add_header(evhttp_request_get_output_headers(request), _key, _value))
|
||||||
|
|
||||||
static int _http_preprocess_request(struct evhttp_request *request, server_s *server) {
|
static int _http_preprocess_request(struct evhttp_request *request, server_s *server) {
|
||||||
RUN(last_request_ts) = get_now_monotonic();
|
RUN(last_request_ts) = get_now_monotonic();
|
||||||
|
|
||||||
|
if (server->allow_origin[0] != '\0') {
|
||||||
|
const char *cors_headers = GET_HEADER("Access-Control-Request-Headers");
|
||||||
|
const char *cors_method = GET_HEADER("Access-Control-Request-Method");
|
||||||
|
|
||||||
|
ADD_HEADER("Access-Control-Allow-Origin", server->allow_origin);
|
||||||
|
ADD_HEADER("Access-Control-Allow-Credentials", "true");
|
||||||
|
if (cors_headers != NULL) {
|
||||||
|
ADD_HEADER("Access-Control-Allow-Headers", cors_headers);
|
||||||
|
}
|
||||||
|
if (cors_method != NULL) {
|
||||||
|
ADD_HEADER("Access-Control-Allow-Methods", cors_method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (evhttp_request_get_command(request) == EVHTTP_REQ_OPTIONS) {
|
||||||
|
evhttp_send_reply(request, HTTP_OK, "OK", NULL);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
if (RUN(auth_token)) {
|
if (RUN(auth_token)) {
|
||||||
const char *token = evhttp_find_header(evhttp_request_get_input_headers(request), "Authorization");
|
const char *token = GET_HEADER("Authorization");
|
||||||
|
|
||||||
if (token == NULL || strcmp(token, RUN(auth_token)) != 0) {
|
if (token == NULL || strcmp(token, RUN(auth_token)) != 0) {
|
||||||
ADD_HEADER("WWW-Authenticate", "Basic realm=\"Restricted area\"");
|
ADD_HEADER("WWW-Authenticate", "Basic realm=\"Restricted area\"");
|
||||||
@@ -228,8 +250,8 @@ static int _http_preprocess_request(struct evhttp_request *request, server_s *se
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (evhttp_request_get_command(request) == EVHTTP_REQ_HEAD) { \
|
if (evhttp_request_get_command(request) == EVHTTP_REQ_HEAD) {
|
||||||
evhttp_send_reply(request, HTTP_OK, "OK", NULL); \
|
evhttp_send_reply(request, HTTP_OK, "OK", NULL);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -464,9 +486,6 @@ static void _http_callback_snapshot(struct evhttp_request *request, void *v_serv
|
|||||||
assert((buf = evbuffer_new()));
|
assert((buf = evbuffer_new()));
|
||||||
assert(!evbuffer_add(buf, (const void *)EX(frame->data), EX(frame->used)));
|
assert(!evbuffer_add(buf, (const void *)EX(frame->data), EX(frame->used)));
|
||||||
|
|
||||||
if (server->allow_origin[0] != '\0') {
|
|
||||||
ADD_HEADER("Access-Control-Allow-Origin", server->allow_origin);
|
|
||||||
}
|
|
||||||
ADD_HEADER("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate, pre-check=0, post-check=0, max-age=0");
|
ADD_HEADER("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate, pre-check=0, post-check=0, max-age=0");
|
||||||
ADD_HEADER("Pragma", "no-cache");
|
ADD_HEADER("Pragma", "no-cache");
|
||||||
ADD_HEADER("Expires", "Mon, 3 Jan 2000 12:34:56 GMT");
|
ADD_HEADER("Expires", "Mon, 3 Jan 2000 12:34:56 GMT");
|
||||||
@@ -580,6 +599,7 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
|||||||
# define BOUNDARY "boundarydonotcross"
|
# define BOUNDARY "boundarydonotcross"
|
||||||
|
|
||||||
stream_client_s *client = (stream_client_s *)v_client;
|
stream_client_s *client = (stream_client_s *)v_client;
|
||||||
|
struct evhttp_request *request = client->request; // for GET_HEADER
|
||||||
server_s *server = client->server;
|
server_s *server = client->server;
|
||||||
|
|
||||||
long double now = get_now_monotonic();
|
long double now = get_now_monotonic();
|
||||||
@@ -620,9 +640,24 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
|||||||
|
|
||||||
if (client->need_initial) {
|
if (client->need_initial) {
|
||||||
assert(evbuffer_add_printf(buf, "HTTP/1.0 200 OK" RN));
|
assert(evbuffer_add_printf(buf, "HTTP/1.0 200 OK" RN));
|
||||||
|
|
||||||
if (client->server->allow_origin[0] != '\0') {
|
if (client->server->allow_origin[0] != '\0') {
|
||||||
assert(evbuffer_add_printf(buf, "Access-Control-Allow-Origin: %s" RN, client->server->allow_origin));
|
const char *cors_headers = GET_HEADER("Access-Control-Request-Headers");
|
||||||
|
const char *cors_method = GET_HEADER("Access-Control-Request-Method");
|
||||||
|
|
||||||
|
assert(evbuffer_add_printf(buf,
|
||||||
|
"Access-Control-Allow-Origin: %s" RN
|
||||||
|
"Access-Control-Allow-Credentials: true" RN,
|
||||||
|
client->server->allow_origin
|
||||||
|
));
|
||||||
|
if (cors_headers != NULL) {
|
||||||
|
assert(evbuffer_add_printf(buf, "Access-Control-Allow-Headers: %s" RN, cors_headers));
|
||||||
|
}
|
||||||
|
if (cors_method != NULL) {
|
||||||
|
assert(evbuffer_add_printf(buf, "Access-Control-Allow-Methods: %s" RN, cors_method));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(evbuffer_add_printf(buf,
|
assert(evbuffer_add_printf(buf,
|
||||||
"Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate, pre-check=0, post-check=0, max-age=0" RN
|
"Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate, pre-check=0, post-check=0, max-age=0" RN
|
||||||
"Pragma: no-cache" RN
|
"Pragma: no-cache" RN
|
||||||
@@ -895,7 +930,7 @@ static char *_http_get_client_hostport(struct evhttp_request *request) {
|
|||||||
assert(addr = strdup(peer));
|
assert(addr = strdup(peer));
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *xff = evhttp_find_header(evhttp_request_get_input_headers(request), "X-Forwarded-For");
|
const char *xff = GET_HEADER("X-Forwarded-For");
|
||||||
if (xff) {
|
if (xff) {
|
||||||
if (addr) {
|
if (addr) {
|
||||||
free(addr);
|
free(addr);
|
||||||
@@ -918,3 +953,5 @@ static char *_http_get_client_hostport(struct evhttp_request *request) {
|
|||||||
free(addr);
|
free(addr);
|
||||||
return hostport;
|
return hostport;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#undef GET_HEADER
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ m2m_encoder_s *m2m_h264_encoder_init(const char *name, const char *path, unsigne
|
|||||||
|
|
||||||
m2m_encoder_s *m2m_mjpeg_encoder_init(const char *name, const char *path, unsigned quality) {
|
m2m_encoder_s *m2m_mjpeg_encoder_init(const char *name, const char *path, unsigned quality) {
|
||||||
const double b_min = 25;
|
const double b_min = 25;
|
||||||
const double b_max = 25000;
|
const double b_max = 20000;
|
||||||
const double step = 25;
|
const double step = 25;
|
||||||
double bitrate = log10(quality) * (b_max - b_min) / 2 + b_min;
|
double bitrate = log10(quality) * (b_max - b_min) / 2 + b_min;
|
||||||
bitrate = step * round(bitrate / step);
|
bitrate = step * round(bitrate / step);
|
||||||
|
|||||||
@@ -20,16 +20,6 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#ifdef NDEBUG
|
|
||||||
# error WTF dude? Asserts are good things!
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <limits.h>
|
|
||||||
#if CHAR_BIT != 8
|
|
||||||
# error There are not 8 bits in a char!
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
|||||||
@@ -439,7 +439,7 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
|
|||||||
ADD_SINK("", sink, SINK)
|
ADD_SINK("", sink, SINK)
|
||||||
ADD_SINK("raw-", raw_sink, RAW_SINK)
|
ADD_SINK("raw-", raw_sink, RAW_SINK)
|
||||||
ADD_SINK("h264-", h264_sink, H264_SINK)
|
ADD_SINK("h264-", h264_sink, H264_SINK)
|
||||||
case _O_H264_BITRATE: OPT_NUMBER("--h264-bitrate", stream->h264_bitrate, 25, 25000, 0);
|
case _O_H264_BITRATE: OPT_NUMBER("--h264-bitrate", stream->h264_bitrate, 25, 20000, 0);
|
||||||
case _O_H264_GOP: OPT_NUMBER("--h264-gop", stream->h264_gop, 0, 60, 0);
|
case _O_H264_GOP: OPT_NUMBER("--h264-gop", stream->h264_gop, 0, 60, 0);
|
||||||
case _O_H264_M2M_DEVICE: OPT_SET(stream->h264_m2m_path, optarg);
|
case _O_H264_M2M_DEVICE: OPT_SET(stream->h264_m2m_path, optarg);
|
||||||
# undef ADD_SINK
|
# undef ADD_SINK
|
||||||
|
|||||||
Reference in New Issue
Block a user