ustreamer: add package 6.51

With mjpg-streamer pending removal [1], it would be nice if we add
a compatible replacement which is under active development.

ustreamer offers a better performance, especially when the
camera supports MJPEG encoding in hardware.

The package already includes OpenWRT support files ./pkg/openwrt
but they needed heavy editing, so it is more efficient to copy
the scripts and configuration, instead of using patches.

Notable changes:
While the init.d script can run in the background when no camera is
connected, it is more efficient to indicate no active instances.
A hotplug script is introduced to start and stop the service when
cameras are added or removed.

If the configured format or encoding are unsupported, a compatible
alternative is automatically selected, so I changed the default
configuration to use MJPEG encoding in hardware for better performance.

HACKS:
MAKE_FLAGS += WITH_SETPROCTITLE=0
is added to workaround the following linker error:
undefined reference to setproctitle_init

This symbol is defined in libbsd, however adding the build dependency
does not resolve the error, because -lbsd is added conditionally, only
when uname -s contains linux. This is unreliable and fails when
cross-compiling on a macOS host. An upstream fix is needed.

An alternative is to use
PKG_UNPACK=$(HOST_TAR) -C $(PKG_BUILD_DIR) --strip=2 -xf $(DL_DIR)/$(PKG_SOURCE)
however this modifies the directory structure, so patches would need
path editing to maintain upstream compatibility.

TODO:
luci-app-mjpg-streamer which is also pending removal [2] is able to
open the HTTP stream from ustreamer. It would be nice to create
luci-app-ustreamer based on that.

[1] https://github.com/openwrt/packages/pull/28344
[2] https://github.com/openwrt/luci/pull/8221

Signed-off-by: Georgi Valkov <gvalkov@gmail.com>
This commit is contained in:
Georgi Valkov
2026-02-01 03:07:13 +02:00
committed by Hannu Nyman
parent 849db7361d
commit ab7fbfd12a
4 changed files with 451 additions and 0 deletions

View File

@@ -0,0 +1,53 @@
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#
include $(TOPDIR)/rules.mk
PKG_NAME:=ustreamer
PKG_VERSION:=6.51
PKG_RELEASE:=1
PKG_MAINTAINER:=Georgi Valkov <gvalkov@gmail.com>
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/pikvm/ustreamer/tar.gz/v$(PKG_VERSION)?
PKG_HASH:=384e90b0b8e9669cf903fad10e361401bfb1b2f808be44498e3855a65d7c0145
PKG_LICENSE:=GPL-3.0
PKG_LICENSE_FILES:=LICENSE
include $(INCLUDE_DIR)/package.mk
MAKE_FLAGS += WITH_SETPROCTITLE=0
define Package/ustreamer
SECTION:=multimedia
CATEGORY:=Multimedia
TITLE:=Lightweight and fast MJPEG-HTTP streamer
DEPENDS:=+libatomic +libjpeg +libevent2 +libevent2-pthreads
URL:=https://github.com/pikvm/ustreamer
endef
define Package/ustreamer/description
uStreamer is a lightweight and very quick server to stream MJPEG video
from any V4L2 device to the net. All new browsers have native support
of this video format, as well as most video players such as mplayer,
VLC etc. uStreamer is a part of the PiKVM project designed to stream
VGA and HDMI screencast hardware data with the highest resolution and
FPS possible.
endef
define Package/ustreamer/install
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/ustreamer $(1)/usr/bin/ustreamer
$(INSTALL_BIN) $(PKG_BUILD_DIR)/ustreamer-dump $(1)/usr/bin/ustreamer-dump
$(INSTALL_DIR) $(1)/etc/config
$(INSTALL_CONF) ./files/ustreamer.config $(1)/etc/config/ustreamer
$(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_BIN) ./files/ustreamer.init $(1)/etc/init.d/ustreamer
$(INSTALL_DIR) $(1)/etc/hotplug.d/usb
$(INSTALL_DATA) ./files/ustreamer.hotplug $(1)/etc/hotplug.d/usb/20-ustreamer
endef
$(eval $(call BuildPackage,ustreamer))

View File

@@ -0,0 +1,99 @@
config ustreamer 'video0'
option enabled '1'
option device '/dev/video0'
option device_timeout '5'
option input '0'
# option resolution '1920x1080'
# option resolution '1280x720'
# option resolution '800x600'
# option resolution '864x480'
option resolution '640x480'
option desired_fps '0'
option slowdown '1'
option format 'MJPEG'
# option format 'YUYV'
# option encoder 'CPU'
option encoder 'HW'
option quality '80'
option host '::'
option port '8080'
option user ''
option pass ''
# capturing
# option allow_truncated_frames '0'
# option format_swap_rgb '0'
# option persistent '0'
# option dv_timings '0'
# option tv_standard 'PAL'
# option tv_standard 'NTSC'
# option tv_standard 'SECAM'
# option io_method 'MMAP'
# option io_method 'USERPTR'
# option buffers '3'
# option workers '2'
# option m2m_device '/dev/path'
# option min_frame_size '128'
# option device_error_delay '1'
# HTTP server
# option tcp_nodelay '1'
# option unix_rm '0'
# option static '/www/webcam'
# option unix '/path/to/socket'
# option unix_mode '660'
# option drop_same_frames '0'
# option fake_resolution '640x480'
# option allow_origin ''
# option instance_id ''
# option server_timeout '10'
# JPEG sink
# option jpeg_sink 'name.jpeg'
# option jpeg_sink_mode '660'
# option jpeg_sink_client_ttl '10'
# option jpeg_sink_timeout '1'
# option jpeg_sink_rm '0'
# RAW sink
# option raw_sink 'name.raw'
# option raw_sink_mode '660'
# option raw_sink_client_ttl '10'
# option raw_sink_timeout '1'
# option raw_sink_rm '0'
# H264 sink
# option h264_sink 'name.h264'
# option h264_sink_mode '660'
# option h264_sink_client_ttl '10'
# option h264_sink_timeout '1'
# option h264_sink_rm '0'
# option h264_boost '0'
# option h264_bitrate '5000'
# option h264_gop '30'
# option h264_m2m_device '/dev/path'
# option exit_on_no_clients '0'
# logging
# option log_level '0'
# image control
# option image_default '0'
# option brightness '128'
# option contrast '128'
# option saturation '128'
# option gamma ''
# option gain 'auto'
# option hue ''
# option sharpness '128'
# option color_effect ''
# option white_balance 'auto'
# option white_balance '2000-6500'
# option backlight_compensation '0'
# option flip_horizontal '0'
# option flip_vertical '0'
# option rotate ''

View File

@@ -0,0 +1,10 @@
case "$ACTION" in
add)
# start process
ls /dev/video* > /dev/null && /etc/init.d/ustreamer start
;;
remove)
# stop process
ls /dev/video* > /dev/null || /etc/init.d/ustreamer stop
;;
esac

View File

@@ -0,0 +1,289 @@
#!/bin/sh /etc/rc.common
# Copyright (C) 2009-2026 OpenWrt.org
START=90
STOP=10
USE_PROCD=1
PROG=/usr/bin/ustreamer
start_instance() {
local enabled=0
local device="/dev/video0"
config_get_bool enabled "$1" 'enabled' "$enabled"
[ "$enabled" -eq 0 ] && return
config_get device "$1" 'device' "$device"
[ -n "$device" -a -c "$device" ] || return
# primary
local input=""
local format="YUYV"
local encoder="CPU"
local quality=80
local resolution="640x480"
local desired_fps=0
local slowdown=1
local device_timeout=5
local host="::"
local port=8080
local user=""
local pass=""
# capturing
local allow_truncated_frames=0
local format_swap_rgb=0
local persistent=0
local dv_timings=0
local tv_standard=""
local io_method=""
local buffers=""
local workers=""
local m2m_device=""
local min_frame_size=""
local device_error_delay=""
# HTTP server
local tcp_nodelay=0
local unix_rm=0
local static=""
local unix=""
local unix_mode=""
local drop_same_frames=""
local fake_resolution=""
local allow_origin=""
local instance_id=""
local server_timeout=""
# JPEG sink
local jpeg_sink=""
local jpeg_sink_mode=""
local jpeg_sink_client_ttl=""
local jpeg_sink_timeout=""
local jpeg_sink_rm=0
# RAW sink
local raw_sink=""
local raw_sink_mode=""
local raw_sink_client_ttl=""
local raw_sink_timeout=""
local raw_sink_rm=0
# H264 sink
local h264_sink=""
local h264_sink_mode=""
local h264_sink_client_ttl=""
local h264_sink_timeout=""
local h264_sink_rm=0
local h264_boost=0
local h264_bitrate=""
local h264_gop=""
local h264_m2m_device=""
local exit_on_no_clients=""
# logging
local log_level=""
# image control
local image_default=0
local brightness=""
local contrast=""
local saturation=""
local gamma=""
local gain=""
local hue=""
local sharpness=""
local color_effect=""
local white_balance=""
local backlight_compensation=""
local flip_horizontal=""
local flip_vertical=""
local rotate=""
# primary
config_get input "$1" 'input' "$input"
config_get format "$1" 'format' "$format"
config_get encoder "$1" 'encoder' "$encoder"
config_get quality "$1" 'quality' "$quality"
config_get resolution "$1" 'resolution' "$resolution"
config_get desired_fps "$1" 'desired_fps' "$desired_fps"
config_get_bool slowdown "$1" 'slowdown' "$slowdown"
config_get device_timeout "$1" 'device_timeout' "$device_timeout"
config_get host "$1" 'host' "$host"
config_get port "$1" 'port' "$port"
config_get user "$1" 'user' "$user"
config_get pass "$1" 'pass' "$pass"
# capturing
config_get_bool allow_truncated_frames "$1" 'allow_truncated_frames' "$allow_truncated_frames"
config_get_bool format_swap_rgb "$1" 'format_swap_rgb' "$format_swap_rgb"
config_get_bool persistent "$1" 'persistent' "$persistent"
config_get_bool dv_timings "$1" 'dv_timings' "$dv_timings"
config_get tv_standard "$1" 'tv_standard' "$tv_standard"
config_get io_method "$1" 'io_method' "$io_method"
config_get buffers "$1" 'buffers' "$buffers"
config_get workers "$1" 'workers' "$workers"
config_get m2m_device "$1" 'm2m_device' "$m2m_device"
config_get min_frame_size "$1" 'min_frame_size' "$min_frame_size"
config_get device_error_delay "$1" 'device_error_delay' "$device_error_delay"
# HTTP server
config_get_bool tcp_nodelay "$1" 'tcp_nodelay' "$tcp_nodelay"
config_get_bool unix_rm "$1" 'unix_rm' "$unix_rm"
config_get static "$1" 'static' "$static"
config_get unix "$1" 'unix' "$unix"
config_get unix_mode "$1" 'unix_mode' "$unix_mode"
config_get drop_same_frames "$1" 'drop_same_frames' "$drop_same_frames"
config_get fake_resolution "$1" 'fake_resolution' "$fake_resolution"
config_get allow_origin "$1" 'allow_origin' "$allow_origin"
config_get instance_id "$1" 'instance_id' "$instance_id"
config_get server_timeout "$1" 'server_timeout' "$server_timeout"
# JPEG sink
config_get jpeg_sink "$1" 'jpeg_sink' "$jpeg_sink"
config_get jpeg_sink_mode "$1" 'jpeg_sink_mode' "$jpeg_sink_mode"
config_get jpeg_sink_client_ttl "$1" 'jpeg_sink_client_ttl' "$jpeg_sink_client_ttl"
config_get jpeg_sink_timeout "$1" 'jpeg_sink_timeout' "$jpeg_sink_timeout"
config_get_bool jpeg_sink_rm "$1" 'jpeg_sink_rm' "$jpeg_sink_rm"
# RAW sink
config_get raw_sink "$1" 'raw_sink' "$raw_sink"
config_get raw_sink_mode "$1" 'raw_sink_mode' "$raw_sink_mode"
config_get raw_sink_client_ttl "$1" 'raw_sink_client_ttl' "$raw_sink_client_ttl"
config_get raw_sink_timeout "$1" 'raw_sink_timeout' "$raw_sink_timeout"
config_get_bool raw_sink_rm "$1" 'raw_sink_rm' "$raw_sink_rm"
# H264 sink
config_get h264_sink "$1" 'h264_sink' "$h264_sink"
config_get h264_sink_mode "$1" 'h264_sink_mode' "$h264_sink_mode"
config_get h264_sink_client_ttl "$1" 'h264_sink_client_ttl' "$h264_sink_client_ttl"
config_get h264_sink_timeout "$1" 'h264_sink_timeout' "$h264_sink_timeout"
config_get_bool h264_sink_rm "$1" 'h264_sink_rm' "$h264_sink_rm"
config_get_bool h264_boost "$1" 'h264_boost' "$h264_boost"
config_get h264_bitrate "$1" 'h264_bitrate' "$h264_bitrate"
config_get h264_gop "$1" 'h264_gop' "$h264_gop"
config_get h264_m2m_device "$1" 'h264_m2m_device' "$h264_m2m_device"
config_get exit_on_no_clients "$1" 'exit_on_no_clients' "$exit_on_no_clients"
# logging
config_get log_level "$1" 'log_level' "$log_level"
# image control
config_get_bool image_default "$1" 'image_default' "$image_default"
config_get brightness "$1" 'brightness' "$brightness"
config_get contrast "$1" 'contrast' "$contrast"
config_get saturation "$1" 'saturation' "$saturation"
config_get gamma "$1" 'gamma' "$gamma"
config_get gain "$1" 'gain' "$gain"
config_get hue "$1" 'hue' "$hue"
config_get sharpness "$1" 'sharpness' "$sharpness"
config_get color_effect "$1" 'color_effect' "$color_effect"
config_get white_balance "$1" 'white_balance' "$white_balance"
config_get backlight_compensation "$1" 'backlight_compensation' "$backlight_compensation"
config_get flip_horizontal "$1" 'flip_horizontal' "$flip_horizontal"
config_get flip_vertical "$1" 'flip_vertical' "$flip_vertical"
config_get rotate "$1" 'rotate' "$rotate"
# configure service
procd_open_instance
procd_set_param command "$PROG"
# primary
[ -n "$device" ] && procd_append_param command --device "$device"
[ -n "$input" ] && procd_append_param command --input "$input"
[ -n "$format" ] && procd_append_param command --format "$format"
[ -n "$encoder" ] && procd_append_param command --encoder "$encoder"
[ -n "$quality" ] && procd_append_param command --quality "$quality"
[ -n "$resolution" ] && procd_append_param command --resolution "$resolution"
[ -n "$desired_fps" ] && procd_append_param command --desired-fps "$desired_fps"
[ "$slowdown" -ne "0" ] && procd_append_param command --slowdown
[ -n "$device_timeout" ] && procd_append_param command --device-timeout "$device_timeout"
[ -n "$host" ] && procd_append_param command --host "$host"
[ -n "$port" ] && procd_append_param command --port "$port"
[ -n "$user" ] && procd_append_param command --user "$user"
[ -n "$pass" ] && procd_append_param command --passwd "$pass"
# capturing
[ "$allow_truncated_frames" -ne "0" ] && procd_append_param command --allow-truncated-frames
[ "$format_swap_rgb" -ne "0" ] && procd_append_param command --format-swap-rgb
[ "$persistent" -ne "0" ] && procd_append_param command --persistent
[ "$dv_timings" -ne "0" ] && procd_append_param command --dv-timings
[ -n "$tv_standard" ] && procd_append_param command --tv-standard "$tv_standard"
[ -n "$io_method" ] && procd_append_param command --io-method "$io_method"
[ -n "$buffers" ] && procd_append_param command --buffers "$buffers"
[ -n "$workers" ] && procd_append_param command --workers "$workers"
[ -n "$m2m_device" ] && procd_append_param command --m2m-device "$m2m_device"
[ -n "$min_frame_size" ] && procd_append_param command --min-frame-size "$min_frame_size"
[ -n "$device_error_delay" ] && procd_append_param command --device-error-delay "$device_error_delay"
# HTTP server
[ "$tcp_nodelay" -ne "0" ] && procd_append_param command --tcp-nodelay
[ "$unix_rm" -ne "0" ] && procd_append_param command --unix-rm
[ -n "$static" ] && procd_append_param command --static "$static"
[ -n "$unix" ] && procd_append_param command --unix "$unix"
[ -n "$unix_mode" ] && procd_append_param command --unix-mode "$unix_mode"
[ -n "$drop_same_frames" ] && procd_append_param command --drop-same-frames "$drop_same_frames"
[ -n "$fake_resolution" ] && procd_append_param command --fake-resolution "$fake_resolution"
[ -n "$allow_origin" ] && procd_append_param command --allow-origin "$allow_origin"
[ -n "$instance_id" ] && procd_append_param command --instance-id "$instance_id"
[ -n "$server_timeout" ] && procd_append_param command --server-timeout "$server_timeout"
# JPEG sink
[ -n "$jpeg_sink" ] && procd_append_param command --jpeg-sink "$jpeg_sink"
[ -n "$jpeg_sink_mode" ] && procd_append_param command --jpeg-sink-mode "$jpeg_sink_mode"
[ -n "$jpeg_sink_client_ttl" ] && procd_append_param command --jpeg-sink-client-ttl "$jpeg_sink_client_ttl"
[ -n "$jpeg_sink_timeout" ] && procd_append_param command --jpeg-sink-timeout "$jpeg_sink_timeout"
[ "$jpeg_sink_rm" -ne "0" ] && procd_append_param command --jpeg-sink-rm
# RAW sink
[ -n "$raw_sink" ] && procd_append_param command --raw-sink "$raw_sink"
[ -n "$raw_sink_mode" ] && procd_append_param command --raw-sink-mode "$raw_sink_mode"
[ -n "$raw_sink_client_ttl" ] && procd_append_param command --raw-sink-client-ttl "$raw_sink_client_ttl"
[ -n "$raw_sink_timeout" ] && procd_append_param command --raw-sink-timeout "$raw_sink_timeout"
[ "$raw_sink_rm" -ne "0" ] && procd_append_param command --raw-sink-rm
# H264 sink
[ -n "$h264_sink" ] && procd_append_param command --h264-sink "$h264_sink"
[ -n "$h264_sink_mode" ] && procd_append_param command --h264-sink-mode "$h264_sink_mode"
[ -n "$h264_sink_client_ttl" ] && procd_append_param command --h264-sink-client-ttl "$h264_sink_client_ttl"
[ -n "$h264_sink_timeout" ] && procd_append_param command --h264-sink-timeout "$h264_sink_timeout"
[ "$h264_sink_rm" -ne "0" ] && procd_append_param command --h264-sink-rm
[ "$h264_boost" -ne "0" ] && procd_append_param command --h264-boost
[ -n "$h264_bitrate" ] && procd_append_param command --h264-bitrate "$h264_bitrate"
[ -n "$h264_gop" ] && procd_append_param command --h264-gop "$h264_gop"
[ -n "$h264_m2m_device" ] && procd_append_param command --h264-m2m-device "$h264_m2m_device"
[ -n "$exit_on_no_clients" ] && procd_append_param command --exit-on-no-clients "$exit_on_no_clients"
# logging
[ -n "$log_level" ] && procd_append_param command --log-level "$log_level"
# image control
[ "$image_default" -ne "0" ] && procd_append_param command --image-default
[ -n "$brightness" ] && procd_append_param command --brightness "$brightness"
[ -n "$contrast" ] && procd_append_param command --contrast "$contrast"
[ -n "$saturation" ] && procd_append_param command --saturation "$saturation"
[ -n "$gamma" ] && procd_append_param command --gamma "$gamma"
[ -n "$gain" ] && procd_append_param command --gain "$gain"
[ -n "$hue" ] && procd_append_param command --hue "$hue"
[ -n "$sharpness" ] && procd_append_param command --sharpness "$sharpness"
[ -n "$color_effect" ] && procd_append_param command --color-effect "$color_effect"
[ -n "$white_balance" ] && procd_append_param command --white-balance "$white_balance"
[ -n "$backlight_compensation" ] && procd_append_param command --backlight-compensation "$backlight_compensation"
[ -n "$flip_horizontal" ] && procd_append_param command --flip-horizontal "$flip_horizontal"
[ -n "$flip_vertical" ] && procd_append_param command --flip-vertical "$flip_vertical"
[ -n "$rotate" ] && procd_append_param command --rotate "$rotate"
procd_add_mdns http tcp "$port" "daemon=ustreamer"
procd_set_param respawn 3600 5 5
procd_close_instance
}
start_service() {
config_load 'ustreamer'
config_foreach start_instance 'ustreamer'
}
service_triggers() {
procd_add_reload_trigger 'ustreamer'
}