Hosting WebRTC Server on IP Camera

Konrad Przewล‚oka

Konrad Przewล‚oka

Software Developer @ Husarnet

Popular industrial and home monitoring systems are based on a central media server that connects cameras with the end users. This architecture is fine for most use cases, however have some drawbacks such as: higher latency, privacy concerns (if you use 3rd party server), and high cost.

In the article we present a peer-to-peer alternative: let's remove a media server and directly access a camera streaming service running on the camera itself.

The project is based on WebRTC for audio and video streaming to a web browser. Access to the server over the Internet is possible thanks to Husarnet VPN Client.

Here are some of the advantages of our solution:

  • low latency over the Internet
  • simple infrastructure architecture (only your laptop and Internet camera)
  • quick setup (everything is dockerized)

Basically all WebRTC infrastructure is hosted on the Internet camera (Single Board Computer + webcam) together with a simple web server.

system setup

TL;DR

Use the prebuilt husarnet/webrtc-streamer:latest Docker image from our Docker Hub account to run the project faster.

๐Ÿ‘‰ https://hub.docker.com/r/husarnet/webrtc-streamer ๐Ÿ‘ˆ

Supported architectures:

  • linux/amd64 (Intel x64)
  • linux/arm64 (eg. Raspberry Pi 4)
  • linux/arm/v7

Otherwise, the following steps will show you how to build and run a container by yourself.

It's Open Source ๐Ÿ˜Ž

You will find a complete source code allowing you to modify and build a Docker image on our GitHub:

https://github.com/husarnet/webrtc-streamer

About WebRTC#

WebRTC is a technology designed for web browsers for a real-time audio and video streaming. It is commonly used in teleconferencing products like Google Meet, Jitsi or TokBox to mention a few of them. External WebRTC servers help web browsers in establishing a real-time connection over the Internet.

In the project we run the WebRTC server not on external server, but on the Internet camera itself. That makes the infrastructure maintanance and setup far easier. Establishing P2P connection is done by Husarnet VPN, so we do not need to host WebRTC servers with a static IP any more.

When it comes to WebRTC streaming there are multiple options available, including but not limited to:

In this project we have choosen Janus as it's a free, open source soultion with relatively easy installation and configuration. In combination wtih FFmpeg, a simple websocket server written in Python and utilizing Husarnet's P2P connection establishment, we are able to provide video stream over WAN with latency as low as 200 - 400 ms.

Why use WebRTC?

One potential question about the technical feasibility of this project may be "Why use WebRTC instead of simple RTSP server?". Anwser to this question is quite straight forward:

RTSP is not directly supported by web browsers.

Requirements#

  1. A single board computer (SBC) with connected USB camera

  2. End user's laptop running Linux with Firefox or Chrome web browser to access a video stream over the Internet.

  3. Husarnet VPN Join Code.

    You will find your Join Code at https://app.husarnet.com
    -> Click on the desired network
    -> Add element button
    -> Join code tab

The whole project runs in a Docker Container.

A SBC with a connected USB camera is in our case Raspberry Pi 4 running Ubuntu 20.04 and Logitech C920 (the old version of C920 had embedded H.264 support - current model unfortunately do not).

If connected USB camera provides a H.264 stream, then this stream is directly used by a WebRTC server. If not, the FFmpeg VP8 codec is used.

To access the webserver with a video stream over the Internet it is required to be in the same Husarnet VPN network. So basically to access a video stream over the Internet install Husarnet VPN client on your laptop and add it to the same Husarnet network.

info

Image has been build and run on the following clear installations of host systems:

Laptop with a built-in webcam
Operating System: Ubuntu 20.04.2 LTS
Kernel: Linux 5.8.0-44-generic
Architecture: x86-64
Raspberry Pi with Logitech C920 camera connected
Operating System: Raspbian GNU/Linux 10 (buster)
Kernel: Linux 5.4.83-v7l+
Architecture: armv7l

Installing Docker#

If you have Docker already installed - you can skip this step.

The official instruction is the best tutorial but here's a quick rundown for you:

sudo -E apt-get -y install apt-transport-https ca-certificates software-properties-common && \
curl -sL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - && \
arch=$(dpkg --print-architecture) && \
sudo -E add-apt-repository "deb [arch=${arch}] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" && \
sudo -E apt-get update && \
sudo -E apt-get -y install docker-ce docker-compose
sudo systemctl daemon-reload
sudo systemctl restart docker

Clonning the Example and Building an Image#

All code is in the GitHub repository:

git clone https://github.com/husarnet/webrtc-streamer

Ensure bash scrpit is executable.

sudo chmod +x init-container.sh

Then build an image.

sudo docker build -t webrtc-streamer .
Raspberry Pi Note

On Raspberry Pi OS you may see a signature error when trying to build the image. In order to solve this you need to manually install the latest libseccomp2.

To do so go to: https://packages.debian.org/sid/libseccomp2 and download armhf version.

Then install it as such:

sudo dpkg -i libseccomp2_2.4.3-1+b1_armhf.deb

Starting the Project Using docker run#

Creating .env File:#

After you created it, specify the Husarnet JoinCode and hostname there. Also change CAM_AUDIO_CHANNELS to =1 if you can't hear a sound. The file should look something like this:

HOSTNAME=webrtc-streamer-1
JOINCODE=fc94:b01d:1803:8dd8:3333:2222:1234:1111/xxxxxxxxxxxxxxxxx
CAM_AUDIO_CHANNELS=2

You will find your JoinCode at https://app.husarnet.com
-> Click on the desired network
-> Add element button
-> Join code tab

Running a Container#

sudo docker run --rm -it \
--env-file ./.env \
--volume webrtc_streamer_v:/var/lib/husarnet \
--device /dev/net/tun \
--device /dev/video0:/dev/video0 \
--device /dev/snd \
--cap-add NET_ADMIN \
--sysctl net.ipv6.conf.all.disable_ipv6=0 \
webrtc-streamer

description:

  • --volume webrtc_streamer_v:/var/lib/husarnet - you need to make /var/lib/husarnet as a volume to preserve it's state for example if you would like to update the image your container is based on. If you would like to run multiple containers on your host machine remember to provide unique volume name for each container (in our case HOSTNAME-v).
  • --device /dev/video0:/dev/video0 - you need to give the container access to your webcam in this case /dev/video0 which will be referenced in the pipline.sh script as /dev/video0

Result#

Runing above commands should result in the following output:

SBC + webcam shell
$ sudo docker run --rm -it --env-file ./.env --volume webrtc_streamer_v:/var/lib/husarnet --device /dev/net/tun --device /dev/video0:/dev/video0 --device /dev/snd --cap-add NET_ADMIN --sysctl net.ipv6.conf.all.disable_ipv6=0 webrtc-streamer
โณ [1/2] Initializing Husarnet Client:
waiting...
waiting...
waiting...
waiting...
success
๐Ÿ”ฅ [2/2] Connecting to Husarnet network as "webrtc-streamer-1":
[5031000] joining...
[5033001] joining...
done
*******************************************
๐Ÿ’ก Tip
To access a live video stream visit:
๐Ÿ‘‰ http://[fc94:4090:c101:c65e:ef7a:fcf1:6789:3b51]:80/ ๐Ÿ‘ˆ
in your web browser ๐Ÿ’ป
*******************************************
H264
Janus commit: 414edcae7955b924f8a434909fafe243c2ad8d6c
Compiled on: Tue Mar 23 09:43:22 UTC 2021
Logger plugins folder: /opt/janus/lib/janus/loggers
[WARN] Couldn't access logger plugins folder...
---------------------------------------------------
Starting Meetecho Janus (WebRTC Server) v0.11.1
---------------------------------------------------
Checking command line arguments...
Debug/log level is 0
Debug/log timestamps are disabled
Debug/log colors are enabled
h264_supp
ffmpeg version 4.2.4-1ubuntu0.1 Copyright (c) 2000-2020 the FFmpeg developers
built with gcc 9 (Ubuntu 9.3.0-10ubuntu2)
configuration: --prefix=/usr --extra-version=1ubuntu0.1
...
libpostproc 55. 5.100 / 55. 5.100
Guessed Channel Layout for Input Stream #0.0 : mono
Input #0, alsa, from 'hw: 1':
Duration: N/A, start: 1616493155.444667, bitrate: 768 kb/s
Stream #0:0: Audio: pcm_s16le, 48000 Hz, mono, s16, 768 kb/s
Stream mapping:
Stream #0:0 -> #0:0 (pcm_s16le (native) -> opus (libopus))
Press [q] to stop, [?] for help
Output #0, rtp, to 'rtp://localhost:8007':
Metadata:
encoder : Lavf58.29.100
Stream #0:0: Audio: opus (libopus), 48000 Hz, mono, s16, 16 kb/s
Metadata:
encoder : Lavc58.54.100 libopus
SDP:
v=0
o=- 0 0 IN IP6 ::1
s=No Name
c=IN IP6 ::1
t=0 0
a=tool:libavformat 58.29.100
m=audio 8007 RTP/AVP 97
b=AS:16
a=rtpmap:97 opus/48000/2
Input #0, video4linux2,v4l2, from '/dev/video0':
Duration: N/A, start: 5035.942407, bitrate: N/A
Stream #0:0: Video: h264 (High), yuvj420p(pc, bt470bg/bt470bg/bt709, progressive), 320x240, 30 fps, 30 tbr, 1000k tbn, 2000k tbc
Output #0, rtp, to 'rtp://localhost:8005':
Metadata:
encoder : Lavf58.29.100
Stream #0:0: Video: h264 (High), yuvj420p(pc, bt470bg/bt470bg/bt709, progressive), 320x240, q=2-31, 30 fps, 30 tbr, 90k tbn, 1000k tbc
SDP:
v=0
o=- 0 0 IN IP6 ::1
s=No Name
c=IN IP6 ::1
t=0 0
a=tool:libavformat 58.29.100
m=video 8005 RTP/AVP 96
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1; sprop-parameter-sets=Z2QAFKwsaoUH6bgoCCgQ,aO4xshsA; profile-level-id=640014
Stream mapping:
Stream #0:0 -> #0:0 (copy)
Press [q] to stop, [?] for help
[rtp @ 0xaaaae5965570] Timestamps are unset in a packet for stream 0. This is deprecated and will stop working in the future. Fix your code to set the timestamps properly
frame= 265 fps= 31 q=-1.0 size= 1398kB time=00:00:08.76 bitrate=1306.7kbits/s speed=1.02x

Accessing a Web User Interface and a Stream#

To access a web UI and video stream hosted by the SBC, you need to connect your computer running Firefox or Chrome web browser to the same Husarnet network as the SBC.

Simply use the same Husarnet Join Code as used befor for SBC

  1. Save your Husarnet VPN Join Code as an environmental variable:

    export HUSARNET_JOINCODE="fc94:b01d:1803:8dd8:3333:2222:1234:1111/xxxxxxxxxxxxxxxxx"
  2. Install Husarnet VPN client:

    curl https://install.husarnet.com/install.sh | sudo bash
    sudo systemctl restart husarnet

    For more detail about installation of the VPN client and managing networks go to https://husarnet.com/docs/begin-linux

  3. Connect your laptop to the same Husarnet VPN network as SBC, by using the same Husarnet Join Code:

    sudo husarnet join ${HUSARNET_JOINCODE} mylaptop
  4. Open the URL provided by the SBC in Firefox or Chrome web browser on your laptop:

    streamer ui

    With stream parameters panel after expansion looking as such

    streamer ui expanded

info

Due to the way Janus handles networking you may need to disable mDNS in your browser setttings in order to view the stream.

On FireFox open URL: about:config and setup:

mdns -> false
media.peerconnection.ice.obfuscate_host_addresses -> false

Remarks#

tip

Lowering latency#

In order to stream with lowest latency possible it is advisable to use a camera that offers feed preencoded with the H264 codec (such as Logitech C920 which was used during testing). The application detects camera support for H264 codec and makes FFmpeg take advantage of it, which reduces latency significantly. However any camera that offers feed in YUVU format is supported.

On Linux you can check available output stream options for your USB camera like that:

$ sudo v4l2-ctl --list-formats
ioctl: VIDIOC_ENUM_FMT
Type: Video Capture
[0]: 'YUYV' (YUYV 4:2:2)
[1]: 'MJPG' (Motion-JPEG, compressed)
[2]: 'H264' (H.264, compressed)

Another important thing to remember is to ensure that a P2P connection has been established between hosts, connection status is detected by server running in the container and sent to UI which displays connection status int the lower part of the screen.

If you face any issues in establishing a P2P connection read our troubleshotting guide

tip

Audio streaming#

By default audio is streamed alongside the video, and can be muted in the UI.

If You don't want to stream audio altogether in order to preserve CPU or bandwith it can be stoped by setting the AUDIO environment variable to false when running the container this results in stoping aduio streaming and encoding.

tip

Testing feed#

If You don't have a camera at hand or just want to quickly test the stream without bothering with the hardware aspects of the setup it is possible to use a feed generated by ffmpeg rather than one coming from a camera. In order to do so just set he TEST environment variable to false when running the container

tip

Adding support for different codecs#

You can quite easily add support for any of the WebRTC used codecs. By implementing Your own functions that setup ffmpeg pipelines and caling them in appropiate places in websocket server.

Summary#

In this blog post we have shown a simple and fast way to setup WebRTC stream over VPN all inside a docker container.

The solution is quite flexible, with all WebRTC infrastructure contained inside the container which can run on a range of hosts form Raspberry Pi to a standard laptop.

Thanks to high configurabilty of stream parameters, codecs and other options described solution can be adopted in different use cases ranging from telepresence robots remote control to survelience or "baby monitoring".

If you would like to send a comment to this blog post, you can do that on Husarnet Community Forum.