Post

WebCams, RTMP and Raspberry Pi

After hitting the limit on my Pi Clone, I started to wonder if I could do better with a genuine Raspberry Pi. I took a look in my stash of bits, and I found my old Home Assistnt Pi - a PoE Hat equipped Pi with a very dodgy SD card, this should be a perfect test system. And a Logitech C922 HD Stream Cam - something very close to the C920 I have installed on my voron

A quick format of the SD card then I blasted the latest version of Debian Trixie Lite OS from the Raspberry Pi Imager and plugged it into a suitable PoE switch

With SSH access, I was able to bring things up to date and begin configuration

1
2
3
sudo apt-get update
sudo apt-get full-upgrade
sudo apt-get install vim-nox

NGINX

Let’s start with NGINX and RTMP

1
sudo apt-get install nginx libnginx-mod-rtmp

RAM Drive

We will want to use a ram drive for the stream, so let’s do that first

1
2
sudo mkdir /var/www/stream
sudo vi /etc/fstab

Add the following line to the end of /etc/fstab

1
2
3
...
tmpfs /var/www/stream tmpfs nodev,nosuid,size=100M 0 0

1
sudo mount -a

RTMP Setup

Create an RTMP handler

1
sudo vi /etc/nginx/modules-available/rtmp.conf

Add the following contents

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
rtmp {
    server {
        listen 1935;
        chunk_size 4096;
        allow publish 127.0.0.1;
        deny publish all;

        application webcam {
            live on;
            record off;

            hls on;
            hls_path /var/www/stream/hls;
            hls_fragment 5s;
            hls_playlist_length 30s;

            dash on;
            dash_path /var/www/stream/dash;
        }
    }
}

Link it to modules-enabled

1
sudo ln -s /etc/nginx/modules-available/rtmp.conf /etc/nginx/modules-enabled/99-rtmp.conf

Stream Proxy and Stats Monitoring

Now let’s create the NGINX Stats monitoring page and hls and dash stream maps

1
sudo vi /etc/nginx/sites-available/rtmp

Contents

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
server {
    listen 8080;

    root /var/www/html/rtmp;

    # rtmp stat
    location /stat {
        rtmp_stat all;
        rtmp_stat_stylesheet stat.xsl;
    }
    location /stat.xsl {
        root /var/www/html/rtmp;
    }

    # rtmp control
    location /control {
        rtmp_control all;
    }
}

server {
    listen 8088;

    location / {
        add_header Access-Control-Allow-Origin *;
        root /var/www/stream;
    }
}

types {
    application/dash+xml mpd;
}

Next we need to create the rtmp folder and and xsl style sheet

1
2
sudo mkdir /var/www/html/rtmp
sudo wget https://raw.githubusercontent.com/arut/nginx-rtmp-module/refs/heads/master/stat.xsl -P /var/www/html/rtmp

Now make the site active and restart nginx

1
2
sudo ln -s /etc/nginx/sites-available/rtmp /etc/nginx/sites-enabled/rtmp
sudo systemctl restart nginx

FFMPEG

Let’s start a stream and test the output. We’ll be using the h264_v4l2m2m codec - this is the Video4Linux2 Memory-2-Memory codec that takes advantage of hardware streaming and will bring in the necessary codec for the bcm2835 chip of the Raspberry Pi 4

First we will install FFMPEG

1
sudo apt-get install ffmpeg

Then start the stream

1
ffmpeg -f v4l2 -i /dev/video0 -c:v h264_v4l2m2m -an -f flv rtmp://127.0.0.1/webcam/live

Finally, open up our web stream in VLC or similar

http://pi4:8088/hls/live.m3u8

Video4Linux2

The “default” rate for the video will be set by the source. Out of the box, this is probably going to be wrong

1
sudo apt-get install v4l-utils

Query the available formats

1
v4l2-ctl --list-devices

gives some output, including our specific camera:

1
2
3
4
C922 Pro Stream Webcam (usb-0000:01:00.0-1.4):
        /dev/video0
        /dev/video1
        /dev/media3

We can query supported formats

1
v4l2-ctl -d /dev/video0 --list-formats
1
2
3
4
5
ioctl: VIDIOC_ENUM_FMT
        Type: Video Capture

        [0]: 'YUYV' (YUYV 4:2:2)
        [1]: 'MJPG' (Motion-JPEG, compressed)

and for extended details

1
v4l2-ctl -d /dev/video0 --list-formats-ext

(I am summarising only the HD formats I’m interested in below, it’s a long list)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
ioctl: VIDIOC_ENUM_FMT
        Type: Video Capture

        [0]: 'YUYV' (YUYV 4:2:2)
...

                Size: Discrete 1280x720
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)
...
                Size: Discrete 1920x1080
                        Interval: Discrete 0.200s (5.000 fps)
...

        [1]: 'MJPG' (Motion-JPEG, compressed)
...
                Size: Discrete 1280x720
                        Interval: Discrete 0.017s (60.000 fps)
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.042s (24.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)
...                        
                Size: Discrete 1920x1080
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.042s (24.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)

We can change parameters for the video size, format and frame rate

1
v4l2-ctl --device=/dev/video0 --set-fmt-video=width=1920,height=1080

Tuning FFMPEG

We have a few ways we can tune this

If we want more than 5 fps, then we will need to use a lower resolution or non-native stream

Generally, we let the output stream be guided by the input stream. This seems to be more expensive in CPU for mpjpeg inputs, vs raw. This might benefit from a two pass loopback - one to decode mjpeg to yuyv422 and one to take the decompressed mjpeg stream and pipe it out

MJPEG Input

This gave me around 60% CPU usage, and didn’t do well

1
2
ffmpeg -f v4l2 -input_format mjpeg -framerate 10 -video_size 1280x720 -i /dev/video0 -pix_fmt yuyv
422 -c:v h264_v4l2m2m -f flv rtmp://127.0.0.1/webcam/live

Raw Input

This barely registered on CPU load, so it’s likely to be my chosen solution for Raspberry Pi 4’s for the future. The quality was noticeably improved over the MJPEG input option, while that may have had a nominally greater framerate

1
ffmpeg -f v4l2 -input_format mjpeg -framerate 10 -video_size 1280x720 -i /dev/video0 -c:v h264_v4l2m2m -f flv rtmp://127.0.0.1/webcam/live

Gluing it all together

Ultimately, I want this to run automatically when the web server is available and everything is up and running. Memory cost is fairly low for the hls and dash side for a 720p stream, so we can tune the RAMdrive down a little - 20M ought to do it

1
sudo vi /etc/fstab

Edit the tmpfs line

1
2
3
...
tmpfs /var/www/stream tmpfs nodev,nosuid,size=20M 0 0

And remount it

1
2
3
4
sudo systemctl stop nginx
sudo systemctl daemon-reload
sudo remount /var/www/stream
sudo systemctl start nginx

Setup a service file for ffmpeg

1
sudo vi /etc/systemd/system/ffmpeg-rtmp.service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[Unit]
Description=FFMPEG RTMP Service
After=nginx.target
Wants=nginx.service
RequiresMountsFor=/var/www/stream

[Service]
Type=simple
ExecStart=/usr/bin/ffmpeg -f v4l2 -input_format yuyv422 -framerate 10 -video_size 1280x720 -i /dev/video0 -c:v h264_v4l2m2m -f flv rtmp://127.0.0.1/webcam/live
Restart=always
RestartSec=5
StartLimitBurst=5

[Install]
WantedBy=multi-user.target

Then enable and start it

1
2
sudo systemctl enable ffmpeg-rtmp.service
sudo systemctl start ffmpeg-rtmp.service

Pushing to Twitch

Now that we have a nice system that starts up automatically, we can have this push to twitch

Start by logging into twitch and getting the Stream key - this is found on the Creator Dashboard, Settings -> Stream. Copy the primary stream key, it will look something like live_1234567890_abcdef1234567890abcdef12345678

Now go edit the rtmp.conf in nginx modules to add the line push {dest} to the application. replacing {MY_STREAM_KEY} with the one obtained from twitch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
rtmp {
    server {
        listen 1935;
        chunk_size 4096;
        allow publish 127.0.0.1;
        deny publish all;

        application webcam {
            live on;
            record off;

            push rtmp://live.twitch.tv/app/{MY_STREAM_KEY}
            hls on;
            hls_path /var/www/stream/hls;
            hls_fragment 5s;
            hls_playlist_length 30s;

            dash on;
            dash_path /var/www/stream/dash;
        }
    }
}

Restart NGINX and you should find your stream being pushed directly to twitch

1
sudo systemctl restart nginx

We can also adjust our stream to match Twitches recommended broadcast settings:

1
ffmpeg -f v4l2 -input_format yuyv422 -framerate 10 -video_size 1280x720 -i /dev/video0 -c:v h264_v4l2m2m -x264-params "nal-hrd=cbr:force-cfr=1" -g 20 -b:v 1M -bufsize 2M -maxrate 4500K -f flv rtmp://127.0.0.1/webcam/live

Th by setting -b:v 1M -bufsize 2M -maxrate 4500k we aim for 1000Kbit/s and set a max at 4500K (for the 720 stream we are generating). We need keyframes every 2s, which is 20 frames (on the 10fps we are outputting) hence -g 20. On a 30fps stream, that would be -g 60

Finally, we can add audio to the stream

1
arecord -L

Should list our webcam capture - we look for the “hw” devices

1
2
3
hw:CARD=Webcam,DEV=0
    C922 Pro Stream Webcam, USB Audio
    Direct hardware device without any conversions

We can plug that into our ffmpeg service file and add the audio to the stream

1
ExecStart=/usr/bin/ffmpeg -f v4l2 -input_format yuyv422 -framerate 10 -video_size 1280x720 -i /dev/video0 -f alsa -ac 2 -i hw:CARD=Webcam,DEV=0 -c:v h264_v4l2m2m -g 20 -b:v 2M -bufsize 1M -maxrate 4500k -c:a aac -b:a 96k -f flv rtmp://127.0.0.1/webcam/live
1
2
sudo systemctl daemon-reload
sudo systemctl restart ffmpeg-rtmp.service

We can start/stop the stream any time, by simply starting/stopping the ffmpeg-rtmp service

This post is licensed under CC BY 4.0 by the author.