Stream hardware encoded h264 from a Pi 4 + camera module to a webbrowser

python | 2024-04-15
bla

Unfortunately, many blogs/tutorials on the internet will offer a MJPG based solution for streaming the Raspberry Pi4 camera to a browser. This does not scale, as mjpg is not efficient. Offering a h264 stream is a better idea.

We found an outdated project that does this, but it had to be forked in order to make it support newer version of the picamera library.

For our use-case, this project allows us to browse to an URL and remotely observe 3D printing projects, in 1080p 30fps, (near)realtime.

../images/pi4-h264-2.jpg

How It Works

  • Tornado (python) is the webserver, and provides a h264 stream via websockets.
  • jMuxer (javascript) handles muxing the h264 stream (in browser).

Installation

We have Raspbian bookworm:

Linux raspberrypi 6.1.0-rpi7-rpi-v8 #1 SMP PREEMPT Debian 1:6.1.63-1+rpt1 (2023-11-24) aarch64 GNU/Linux

Install dependencies, clone repository:

sudo apt install -y rpicam-apps-lite python3-virtualenv python3-tornado python3-picamera2 libraspberrypi0 libraspberrypi-dev

cd ~/
git clone https://github.com/kroketio/pi-h264-to-browser.git
cd pi-h264-to-browser

Configure the application

Websocket URL

There is one thing you need to change before starting the application, that is changing the websocket URL.

In src/main.py there is:

wsURL = "ws://my_ip/ws/"

Replace it with the IP and port of your device. For example:

wsURL = "ws://192.168.0.7:8000/ws/"

If you proxy-forward behind a webbserver that does TLS, use wss:// instead.

Stream quality

Tip regarding bandwidth: Tweak qp, e.g a value of 26 generates 5.3MBit/s of video data at 1080p, while 19 generates 30MBit/s. For raspberry pi cameras you can get away with lower compression because the cameras themselves are not super high quality. 26 is fine for our use-case.

Starting the application

We can use systemd to create a service so that this application runs automatically on system startup.

Create the following file: /etc/systemd/system/cam.service

Replace /home/user/ with the real path.

[Unit]
Description=pi4 cam web

[Service]
ExecStart=/usr/bin/python3 /home/user/pi-h264-to-browser/src/server.py

[Install]
WantedBy=multi-user.target

Reload systemd to load this new service file:

systemctl daemon-reload

Enable on system boot:

systemctl enable cam

Start the service:

systemctl start cam

The service should now be running on port 8000. You can browse to it with your webbrowser.

You might need to allow port 8000 in the firewall:

ufw allow 8000

troubleshooting

To view application logs:

journalctl -xefu cam

You can also run the application manually:

/usr/bin/python3 /home/user/pi-h264-to-browser/src/server.py

Verify there is a camera present by using libcamera-hello:

libcamera-hello
[1:42:12.046456609] [1443]  INFO Camera camera_manager.cpp:284 libcamera v0.1.0+118-563cd78e
[1:42:12.088202181] [1446]  WARN RPiSdn sdn.cpp:39 Using legacy SDN tuning - please consider moving SDN inside rpi.denoise
[1:42:12.091335080] [1446]  INFO RPI vc4.cpp:444 Registered camera /base/soc/i2c0mux/i2c@1/imx477@1a to Unicam device /dev/media3 and ISP device /dev/media0
[1:42:12.091437949] [1446]  INFO RPI pipeline_base.cpp:1142 Using configuration file '/usr/share/libcamera/pipeline/rpi/vc4/rpi_apps.yaml'
Preview window unavailable
Mode selection for 2028:1520:12:P
    SRGGB10_CSI2P,1332x990/0 - Score: 3456.22
    SRGGB12_CSI2P,2028x1080/0 - Score: 1083.84
    SRGGB12_CSI2P,2028x1520/0 - Score: 0
    SRGGB12_CSI2P,4056x3040/0 - Score: 887
Stream configuration adjusted
[1:42:12.093609472] [1443]  INFO Camera camera.cpp:1183 configuring streams: (0) 2028x1520-YUV420 (1) 2028x1520-SBGGR12_CSI2P
[1:42:12.094110391] [1446]  INFO RPI vc4.cpp:608 Sensor: /base/soc/i2c0mux/i2c@1/imx477@1a - Selected sensor format: 2028x1520-SBGGR12_1X12 - Selected unicam format: 2028x1520-pBCC

Our result