I recently got myself an ArduCam 16MP Autofocus camera to run as a monitoring camera with my Raspberry Pi 4B. It uses an IMX519 sensor, which has libcamera
support in Bullseye, but not raspicam
. In fact, from Raspberry Pi OS Bullseye and on, only libcamera
is supported. As such, it does not work with OctoPi out of the box.
ArduCam has published instructions on how to get OctoPi working with the libcamera
stack (Solution Of Using Autofocus Camera On OctoPrint - Arducam), but in my opinion, their approach is not suitable for running a real OctoPi instance for several reasons:
- It involves modifying a nightly version of OctoPi.
- It is not a good idea to run something as sensitive and potentially dangerous as a 3D printer long term on an unstable nightly build.
- The modifications are not trivially reversible and are difficult to document.
The Docker Way
This method of using libcamera and OctoPrint eschews OctoPi entirely. Instead, this will use the official Raspberry Pi OS image and run a modified mjpg_streamer
and OctoPrint in Docker containers. This approach has several benefits:
- OctoPrint is completely decoupled from the host OS. This means that any urgent OS updates can be immediately applied with negligible risk of affecting the OctoPrint installation.
-
mjpg_streamer
can be easily swapped out with a better solution for streaminglibcamera
cameras in the future, since it runs as just a basic Docker container. - Everything is documented as Dockerfiles and YAML files, meaning the setup is portable and can be easily replicated across multiple installations.
Warning: This is a technical guide
This guide assumes that you are capable of setting up and maintaining a Docker installation. If you are not comfortable with Docker, this may be too complex.
Setting up the camera
Before you can stream from a libcamera
camera, you should first make sure that the camera functions as expected. In my case, I installed the kernel driver given by ArduCam (see ArduCam instructions above):
wget -O install_pivariety_pkgs.sh https://github.com/ArduCAM/Arducam-Pivariety-V4L2-Driver/releases/download/install_script/install_pivariety_pkgs.sh
chmod +x install_pivariety_pkgs.sh
./install_pivariety_pkgs.sh -p imx519_kernel_driver
You can test whether the camera works by installing and running a simple libcamera-still
:
sudo apt install libcamera-apps-lite
libcamera-still
Dockerizing mjpg_streamer
To dockerize mjpg_streamer
with support for libcamera
, I opted to use the fork that ArduCam provided: GitHub - ArduCAM/mjpg-streamer: Fork of http://sourceforge.net/projects/mjpg-streamer/.
I created a Docker image that would download, patch, and build this fork:
Dockerfile
FROM debian:bullseye
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update && apt-get install -y cmake build-essential curl git
RUN echo "deb http://archive.raspberrypi.org/debian/ bullseye main" > /etc/apt/sources.list.d/raspi.list
RUN curl "https://archive.raspberrypi.org/debian/raspberrypi.gpg.key" | apt-key add -
RUN apt-get update && apt-get install -y libcamera-dev libjpeg-dev libgphoto2-dev
RUN git clone https://github.com/ArduCAM/mjpg-streamer.git /src
WORKDIR /src/mjpg-streamer-experimental
RUN sed -i "/input_libcamera/p;/input_/d;/output_http/p;/output_/d" CMakeLists.txt
RUN make && make install
COPY entrypoint.sh entrypoint.sh
ENV INPUT_FLAGS "-r 1920x1080 -f 60"
ENV OUTPUT_FLAGS "-w ./www"
ENTRYPOINT ["./entrypoint.sh"]
entrypoint.sh
#!/bin/sh
input="input_libcamera.so $INPUT_FLAGS"
output="output_http.so $OUTPUT_FLAGS"
./mjpg_streamer -i "$input" -o "$output"
Things to note about this Dockerfile:
- The Raspberry Pi repository was added so that the
libcamera-dev
dependency could be downloaded. - All inputs other than
input_libcamera
and all outputs other thanoutput_http
were removed fromCMakeLists.txt
.- This is primarily to remove the dependency on
libopencv-dev
, which would have involved downloading over 700MB of data.
- This is primarily to remove the dependency on
- This image is not hermetic, meaning ArduCam could make changes to their fork that break the image or even introduce nefarious backdoors at any time.
- Always vet any code you compile and run.
To use this image, all /dev/video*
and /dev/v4l2*
devices need to be passed through to the container. udev also needs to be passed through to the container. In my case, I was lazy and simply ran the container in privileged mode and mounted /run/udev:/run/udev:ro
.
Input flags and output flags are defined using the INPUT_FLAGS
and OUTPUT_FLAGS
environment variables.
OctoPrint
Adding OctoPrint is very straightforward, since an official image is provided: octoprint/octoprint.
Being lazy yet again, I opted to run OctoPrint in privileged mode as well, but you only need to pass through the printer to the container (/dev/ttyACM0
in my case for a Prusa Mini+).
To get a couple of plugins to work better, I also mounted:
- NavbarTemp
/proc/cpuinfo:/proc/cpuinfo:ro
/usr/bin/vcgencmd:/usr/bin/vcgencmd:ro
/usr/lib/aarch64-linux-gnu/libvcos.so.0:/usr/lib/aarch64-linux-gnu/libvcos.so.0:ro
/usr/lib/aarch64-linux-gnu/libvchiq_arm.so.0:/usr/lib/aarch64-linux-gnu/libvchiq_arm.so.0:ro
- CameraSettings (done implicitly through privileged mode)
/dev/video*
/dev/v4l2*
Because mjpg_streamer
is run in a separate container, the OctoPrint container does not need to be configured with ENABLE_MJPG_STREAMER
.
A couple of other settings that will need to be changed from default for OctoPrint and optionally Octolapse to function correctly:
- Settings > OctoPrint > Server > Commands > Restart OctoPrint
s6-svc -r /var/run/s6/services/octoprint
- This allows OctoPrint to signal to the supervisor to restart.
- Settings > Features > Webcam & Timelapse > Webcam > Stream URL
http://<LOCAL_IP>:<PORT>/?action=stream
- The
<LOCAL_IP>:<PORT>
should point to yourmjpg_streamer
instance.
- Settings > Plugins > Octolapse > Camera > [Your Camera] > Webcam Setup
- Base Address:
http://<LOCAL_IP>:<PORT>
- Snapshot Address:
{camera_address}?action=snapshot
- Stream Address Template:
http://<LOCAL_IP>:<PORT>/?action=stream
- Base Address:
docker-compose
The final docker-compose.yaml
I used for the setup was:
version: "3"
services:
mjpg-streamer:
build:
context: ./mjpg-streamer
dockerfile: Dockerfile
restart: always
privileged: true
ports:
- 8080:8080
environment:
- INPUT_FLAGS=-r 1920x1080 -f 30
- OUTPUT_FLAGS=-w ./www
volumes:
- /run/udev:/run/udev:ro
octoprint:
image: octoprint/octoprint:latest
restart: always
privileged: true
ports:
- 80:80
devices:
- /dev/ttyACM0:/dev/ttyACM0
volumes:
- ./data/octoprint:/octoprint
- /proc/cpuinfo:/proc/cpuinfo:ro
- /usr/bin/vcgencmd:/usr/bin/vcgencmd:ro
- /usr/lib/aarch64-linux-gnu/libvcos.so.0:/usr/lib/aarch64-linux-gnu/libvcos.so.0:ro
- /usr/lib/aarch64-linux-gnu/libvchiq_arm.so.0:/usr/lib/aarch64-linux-gnu/libvchiq_arm.so.0:ro
Final thoughts
This approach of using Docker to create a separation of concerns between the OS, OctoPrint, and mjpg_streamer
is, in my opinion, a cleaner and better way to run OctoPrint than through the OctoPi image.
Will I create an image for this?
No. That would defeat the entire point.
Will there be degraded performance due to Docker overhead?
Compared to running natively, there will definitely be a non-zero amount of reduced performance. However, I am running this setup on a Raspberry Pi 4B with 2GB RAM, and performance has not been an issue.
GitHub Files
In addition to the file contents I've pasted above, all the files that make up this setup are in GitHub - ruiqimao/octoprint-libcamera: libcamera with OctoPrint using Docker.