Skip to content

Commit

Permalink
Various changes for usability (#60)
Browse files Browse the repository at this point in the history
* Tkinter was not needed after all
* Allow empty username for basic authentication
* Stay on the safe side and ensure libvpx exists
* Change default encoder to x264enc
* Handle x264enc threads automatically, rollback if there is a reason
* Handle CPU usage automatically with vpx, edit if not optimal
* Refine Xfce4 invocation
* Converge docs and docker compose dev version with Github Actions dev version
* Docs about latency
* Multiple screen instructions
* Edit docs about cursor lock and fullscreen
* Fix issue with the < key layout
* Improve x264enc latency with sliced-threads
  • Loading branch information
ehfd committed Oct 12, 2022
1 parent fe81be5 commit 48329d4
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 82 deletions.
25 changes: 15 additions & 10 deletions Dockerfile.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
ARG UBUNTU_RELEASE=20.04
ARG GSTREAMER_BASE_IMAGE=ghcr.io/selkies-project/selkies-gstreamer/gstreamer
ARG GSTREAMER_BASE_IMAGE_RELEASE=master
ARG PY_BUILD_IMAGE=ghcr.io/selkies-project/selkies-gstreamer/py-build:latest
ARG WEB_IMAGE=ghcr.io/selkies-project/selkies-gstreamer/gst-web:latest
ARG PY_BUILD_IMAGE=ghcr.io/selkies-project/selkies-gstreamer/py-build:master
ARG WEB_IMAGE=ghcr.io/selkies-project/selkies-gstreamer/gst-web:master
FROM ${GSTREAMER_BASE_IMAGE}:${GSTREAMER_BASE_IMAGE_RELEASE}-ubuntu${UBUNTU_RELEASE} as selkies-gstreamer
FROM ${PY_BUILD_IMAGE} as selkies-build
FROM ${WEB_IMAGE} as selkies-web
FROM ubuntu:${UBUNTU_RELEASE}
ARG UBUNTU_RELEASE=20.04
ARG UBUNTU_RELEASE

LABEL maintainer "https://github.com/danisla"

Expand All @@ -20,7 +20,6 @@ RUN \
python3-dev \
python3-gi \
python3-setuptools \
python3-tk \
python3-wheel \
tzdata \
sudo \
Expand All @@ -45,6 +44,7 @@ RUN \
libpangocairo-1.0-0 \
libgirepository1.0-dev \
libjpeg-dev \
libvpx-dev \
zlib1g-dev \
x264 \
git && \
Expand Down Expand Up @@ -98,7 +98,7 @@ RUN \

# Install selkies-gstreamer Python app
ARG PYPI_PACKAGE=selkies_gstreamer
ARG PACKAGE_VERSION=1.0.0.dev0
ARG PACKAGE_VERSION=0.0.0.dev0
COPY --from=selkies-build /opt/pypi/dist/${PYPI_PACKAGE}-${PACKAGE_VERSION}-py3-none-any.whl .
RUN pip3 install /opt/${PYPI_PACKAGE}-${PACKAGE_VERSION}-py3-none-any.whl

Expand All @@ -113,6 +113,8 @@ RUN echo 'export DISPLAY=:0' \
>> /etc/bash.bashrc && \
echo 'export GST_DEBUG=*:2' \
>> /etc/bash.bashrc && \
echo 'export GSTREAMER_PATH=/opt/gstreamer' \
>> /etc/bash.bashrc && \
echo 'source /opt/gstreamer/gst-env' \
>> /etc/bash.bashrc

Expand All @@ -121,15 +123,16 @@ RUN echo "#!/bin/bash\n\
export DISPLAY=:0\n\
export GST_DEBUG=*:2\n\
export PULSE_SERVER=127.0.0.1:4713\n\
export GSTREAMER_PATH=/opt/gstreamer\n\
source /opt/gstreamer/gst-env\n\
Xvfb -screen :0 8192x4096x24 +extension RANDR +extension GLX +extension MIT-SHM -nolisten tcp -noreset -shmem 2>&1 >/tmp/Xvfb.log &\n\
until [[ -S /tmp/.X11-unix/X0 ]]; do sleep 1; done && echo 'X Server is ready'\n\
sudo /usr/bin/pulseaudio -k >/dev/null 2>&1\n\
sudo /usr/bin/pulseaudio --daemonize --system --verbose --log-target=file:/tmp/pulseaudio.log --realtime=true --disallow-exit -L 'module-native-protocol-tcp auth-ip-acl=127.0.0.0/8 port=4713 auth-anonymous=1'\n\
[[ \${START_XFCE4:-true} == true ]] && xfce4-session &\n\
[[ \${START_XFCE4:-true} == true ]] && rm -rf ~/.config/xfce4 && xfce4-session &\n\
export WEBRTC_ENCODER=\${WEBRTC_ENCODER:-x264enc}\n\
export WEBRTC_ENABLE_RESIZE=\${WEBRTC_ENABLE_RESIZE:-true}\n\
export JSON_CONFIG=/tmp/selkies.json\n\
export JSON_CONFIG=/tmp/rtc.json\n\
echo '{}' > \$JSON_CONFIG\n\
selkies-gstreamer-resize 1280x720\n\
selkies-gstreamer\n\
Expand Down Expand Up @@ -169,11 +172,13 @@ RUN groupadd -g 1000 user && \
echo "user:${PASSWD}" | chpasswd && \
ln -snf "/usr/share/zoneinfo/$TZ" /etc/localtime && echo "$TZ" > /etc/timezone

# Prevent dialogs at Xfce4 desktop environment start
RUN cp -rf /etc/xdg/xfce4/panel/default.xml /etc/xdg/xfce4/xfconf/xfce-perchannel-xml/xfce4-panel.xml

USER user
ENV USER=user
WORKDIR /home/user
RUN touch ${HOME}/.sudo_as_admin_successful

# Set default icewm theme
RUN mkdir -p ${HOME}/.icewm && echo 'Theme="NanoBlue/default.theme"' > ${HOME}/.icewm/theme

ENTRYPOINT ["/tini", "--"]
CMD ["/entrypoint.sh"]
90 changes: 50 additions & 40 deletions README.md

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ services:
PY_BUILD_IMAGE: selkies-gstreamer-py-build:latest
WEB_IMAGE: gst-web:latest
PYPI_PACKAGE: selkies_gstreamer
PACKAGE_VERSION: 1.0.0.dev0
PACKAGE_VERSION: 0.0.0.dev0
environment:
# Basic authentication
ENABLE_BASIC_AUTH: ${ENABLE_BASIC_AUTH}
Expand Down Expand Up @@ -71,6 +71,7 @@ services:
volumes:
- type: bind
source: ./src/selkies_gstreamer
# Change python3.8 to python3.6 in 18.04 and python3.10 in 22.04
target: /usr/local/lib/python3.8/dist-packages/selkies_gstreamer
read_only: true

Expand Down
2 changes: 1 addition & 1 deletion src/selkies_gstreamer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import time
sys.path.append(os.path.dirname(os.path.abspath(__file__)))

# Verify gstreamer installation
# Verify GStreamer installation
retry = True
while retry:
try:
Expand Down
38 changes: 19 additions & 19 deletions src/selkies_gstreamer/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ def main():
parser.add_argument('--json_config',
default=os.environ.get(
'JSON_CONFIG', '/var/run/appconfig/streaming_args.json'),
help='Path to JSON file containing argument key-value pairs that are overlayed with cli args/env.')
help='Path to the JSON file containing argument key-value pairs that are overlayed with CLI arguments or environment variables.')
parser.add_argument('--addr',
default=os.environ.get(
'LISTEN_HOST', '0.0.0.0'),
Expand All @@ -294,15 +294,15 @@ def main():
parser.add_argument('--enable_basic_auth',
default=os.environ.get(
'ENABLE_BASIC_AUTH', 'false'),
help='Enable Basic authentication on server. Must set basic_auth_user and basic_auth_password to enforce Basic auth.')
help='Enable Basic authentication on server. Must set basic_auth_password and optionally basic_auth_user to enforce Basic authentication.')
parser.add_argument('--basic_auth_user',
default=os.environ.get(
'BASIC_AUTH_USER', os.environ.get('USER', '')),
help='Username for basic auth, default is to use the USER env var. Must also set basic_auth_password to enforce Basic auth.')
help='Username for Basic authentication, default is to use the USER environment variable or a blank username if it does not exist. Must also set basic_auth_password to enforce Basic authentication.')
parser.add_argument('--basic_auth_password',
default=os.environ.get(
'BASIC_AUTH_PASSWORD', ''),
help='Password used when basic_auth_user is set.')
help='Password used when Basic authentication is set.')
parser.add_argument('--web_root',
default=os.environ.get(
'WEB_ROOT', '/opt/gst-web'),
Expand All @@ -318,15 +318,15 @@ def main():
parser.add_argument('--coturn_auth_header_name',
default=os.environ.get(
'COTURN_AUTH_HEADER_NAME', 'x-auth-user'),
help='header name to pass user to coturn web service')
help='Header name to pass user to coturn web service')
parser.add_argument('--rtc_config_json',
default=os.environ.get(
'RTC_CONFIG_JSON', '/tmp/rtc.json'),
help='JSON file with RTC config to use as alternative to coturn service, read periodically')
parser.add_argument('--turn_shared_secret',
default=os.environ.get(
'TURN_SHARED_SECRET', ''),
help='shared TURN secret used to generate HMAC credentials, also requires TURN_HOST and TURN_PORT.')
help='Shared TURN secret used to generate HMAC credentials, also requires TURN_HOST and TURN_PORT.')
parser.add_argument('--turn_username',
default=os.environ.get(
'TURN_USERNAME', ''),
Expand All @@ -350,37 +350,37 @@ def main():
parser.add_argument('--turn_tls',
default=os.environ.get(
'TURN_TLS', 'false'),
help='enable or disable TURN over TLS (for the TCP protocol) or TURN over DTLS (for the UDP protocol), valid TURN server certificate required.')
help='Enable or disable TURN over TLS (for the TCP protocol) or TURN over DTLS (for the UDP protocol), valid TURN server certificate required.')
parser.add_argument('--uinput_mouse_socket',
default=os.environ.get('UINPUT_MOUSE_SOCKET', ''),
help='path to uinput mouse socket provided by uinput-device-plugin, if not provided, uinput is used directly.')
help='Path to uinput mouse socket provided by uinput-device-plugin, if not provided, uinput is used directly.')
parser.add_argument('--uinput_js_socket',
default=os.environ.get('UINPUT_JS_SOCKET', ''),
help='path to uinput joystick socket provided by uinput-device-plugin, if not provided, uinput is used directly.')
help='Path to uinput joystick socket provided by uinput-device-plugin, if not provided, uinput is used directly.')
parser.add_argument('--enable_audio',
default=os.environ.get('ENABLE_AUDIO', 'true'),
help='enable or disable audio stream')
help='Enable or disable audio stream')
parser.add_argument('--enable_clipboard',
default=os.environ.get('ENABLE_CLIPBOARD', 'true'),
help='enable or disable the clipboard features, supported values: true, false, in, out')
help='Enable or disable the clipboard features, supported values: true, false, in, out')
parser.add_argument('--app_auto_init',
default=os.environ.get('APP_AUTO_INIT', 'true'),
help='if true, skips wait for APP_READY_FILE to exist before starting stream.')
help='If true, skips wait for APP_READY_FILE to exist before starting stream.')
parser.add_argument('--app_ready_file',
default=os.environ.get('APP_READY_FILE', '/var/run/appconfig/appready'),
help='file set by sidecar used to indicate that app is initialized and ready')
help='File set by sidecar used to indicate that app is initialized and ready')
parser.add_argument('--framerate',
default=os.environ.get('WEBRTC_FRAMERATE', '30'),
help='framerate of streaming pipeline')
help='Framerate of streaming pipeline')
parser.add_argument('--video_bitrate',
default=os.environ.get('WEBRTC_VIDEO_BITRATE', '2000'),
help='default video bitrate')
help='Default video bitrate')
parser.add_argument('--audio_bitrate',
default=os.environ.get('WEBRTC_AUDIO_BITRATE', '64000'),
help='default audio bitrate')
help='Default audio bitrate')
parser.add_argument('--encoder',
default=os.environ.get('WEBRTC_ENCODER', 'nvh264enc'),
help='gstreamer encoder plugin to use')
default=os.environ.get('WEBRTC_ENCODER', 'x264enc'),
help='GStreamer encoder plugin to use')
parser.add_argument('--enable_resize',
default=os.environ.get('WEBRTC_ENABLE_RESIZE', 'true'),
help='Enable dynamic resizing to match browser size')
Expand All @@ -389,7 +389,7 @@ def main():
help='Enable passing remote cursors to client')
parser.add_argument('--metrics_port',
default=os.environ.get('METRICS_PORT', '8000'),
help='port to start metrics server on')
help='Port to start metrics server on')
parser.add_argument('--debug', action='store_true',
help='Enable debug logging')
args = parser.parse_args()
Expand Down
8 changes: 3 additions & 5 deletions src/selkies_gstreamer/gstwebrtc_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ def build_video_pipeline(self):

# encoder
x264enc = Gst.ElementFactory.make("x264enc", "x264enc")
x264enc.set_property("threads", 4)
x264enc.set_property("sliced-threads", True)
x264enc.set_property("bframes", 0)
x264enc.set_property("key-int-max", 0)
x264enc.set_property("byte-stream", True)
Expand Down Expand Up @@ -359,7 +359,6 @@ def build_video_pipeline(self):

if self.encoder == "vp9enc":
vpenc = Gst.ElementFactory.make("vp9enc", "vpenc")
vpenc.set_property("threads", 4)
vpenc_caps = Gst.caps_from_string("video/x-vp9")
vpenc_capsfilter = Gst.ElementFactory.make("capsfilter")
vpenc_capsfilter.set_property("caps", vpenc_caps)
Expand All @@ -374,7 +373,6 @@ def build_video_pipeline(self):

# VPX Parameters
# Borrowed from: https://github.com/nurdism/neko/blob/df98368137732b8aaf840e27cdf2bd41067b2161/server/internal/gst/gst.go#L94
vpenc.set_property("threads", 2)
vpenc.set_property("cpu-used", 8)
vpenc.set_property("deadline", 1)
vpenc.set_property("error-resilient", "partitions")
Expand Down Expand Up @@ -696,7 +694,7 @@ def set_framerate(self, framerate):
logger.info("framerate set to: %d" % framerate)

def set_video_bitrate(self, bitrate):
"""Set NvEnc encoder target bitrate in bps
"""Set video encoder target bitrate in bps
Arguments:
bitrate {integer} -- bitrate in bits per second, for example, 2000 for 2kbits/s or 10000 for 1mbit/sec.
Expand Down Expand Up @@ -948,7 +946,7 @@ def __send_ice(self, webrtcbin, mlineindex, candidate):
loop.run_until_complete(self.on_ice(mlineindex, candidate))

def start_pipeline(self):
"""Starts the gstreamer pipeline
"""Starts the GStreamer pipeline
"""

logger.info("starting pipeline")
Expand Down
10 changes: 5 additions & 5 deletions src/selkies_gstreamer/signalling_web.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,13 +140,13 @@ def __init__(self, loop, options):

# Validate TURN args
if self.turn_shared_secret:
if not self.turn_host and self.turn_port:
raise Exception("missing turn_host and turn_port options with turn_shared_secret")
if not (self.turn_host and self.turn_port):
raise Exception("missing turn_host or turn_port options with turn_shared_secret")

# Validate basic auth args
if self.enable_basic_auth:
if not (self.basic_auth_user and self.basic_auth_password):
raise Exception("missing basic_auth_password when using basic_auth_user option.")
if not self.basic_auth_password:
raise Exception("missing basic_auth_password when using enable_basic_auth option.")

############### Helper functions ###############

Expand Down Expand Up @@ -513,7 +513,7 @@ def main():
parser.add_argument('--health', default='/health', help='Health check route')
parser.add_argument('--restart-on-cert-change', default=False, dest='cert_restart', action='store_true', help='Automatically restart if the SSL certificate changes')
parser.add_argument('--enable_basic_auth', default="false", help="Use basic auth, must also set basic_auth_user, and basic_auth_password args")
parser.add_argument('--basic_auth_user', default="", help='Username for basic auth, if not set, no authorization will be enforced.')
parser.add_argument('--basic_auth_user', default="", help='Username for basic auth.')
parser.add_argument('--basic_auth_password', default="", help='Password for basic auth, if not set, no authorization will be enforced.')

options = parser.parse_args(sys.argv[1:])
Expand Down
7 changes: 6 additions & 1 deletion src/selkies_gstreamer/webrtc_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ def __js_emit(self, *args, **kwargs):
async def connect(self):
"""Connects to X server
The target X server is determiend by the DISPLAY environment variable.
The target X server is determined by the DISPLAY environment variable.
"""

self.xdisplay = display.Display()
Expand Down Expand Up @@ -304,6 +304,11 @@ def send_x11_keypress(self, keysym, down=True):
down {bool} -- toggle key down or up (default: {True})
"""

# With the Generic 105-key PC layout (default in Linux without a real keyboard), the key '<' is redirected to keycode 94
# Because keycode 94 with Shift pressed is instead the key '>', the keysym for '<' should instead be redirected to ','
# Although prevented in most cases, this fix may present issues in some keyboard layouts
if keysym == 60 and self.keyboard._display.keysym_to_keycode(keysym) == 94:
keysym = 44
keycode = pynput.keyboard.KeyCode(keysym)
if down:
self.keyboard.press(keycode)
Expand Down

0 comments on commit 48329d4

Please sign in to comment.