Working front and rear cameras on Debian 11 on a Surface Pro 6, SurfaceBook 2, and Surface Go

screenshot of me using jitsi

Update: I’ve tested this on Debian 10 and Debian 11, and on a Surface Pro 6, a SurfaceBook 2, and a Surface Go.

Linux support for the Surface Pro 6 is quite superb, thanks to the people working on the linux-surface project.

One of the last hold-outs was support for the cameras.

The Surface Pro 6 seems to have been luckier than the other devices in the Surface line, in that now there is support for both cameras, albeit the quality is not great.

There is no single set of instructions to help make the cameras work, and make them usable in applications (e.g. firefox, for use in jitsi), so I wrote up what I learned for the github issues list, and I’m repeating it here.

Install the ipu3-fw.bin firmware

You can get the firmware you need for the cameras from firmware-linux, but you might need to change the name of a file. Here’s a workaround:

cd /tmp
wget -4 http://archive.ubuntu.com/ubuntu/pool/main/l/linux-firmware/linux-firmware_1.187.tar.gz
tar -xvf linux-firmware_1.187.tar.gz
sudo cp /tmp/linux-firmware/intel/irci_irci_ecr-master_20161208_0213_20170112_1500.bin /lib/firmware/intel/ipu3-fw.bin
rm -r /tmp/linux-firmware
rm -r /tmp/linux-firmware_1.187.tar.gz

Remove any existing libcamera files/config

Camera support relies on libcamera, and I had previously built this, and got cameras working on qcam, but not available in applications. For this, one needs gstreamer support.

It might be possible to re-do the initial configuration step to make this work, but I failed, so I just removed the libcamera repository as my starting step:

cd ~
sudo rm -r libcamera

Clone libcamera from GitHub

git clone https://git.libcamera.org/libcamera/libcamera.git

Install components

 sudo apt install build-essential meson ninja-build pkg-config libgnutls28-dev openssl python3-pip python3-yaml python3-ply python3-jinja2 qtbase5-dev libqt5core5a libqt5gui5 libqt5widgets5 qttools5-dev-tools libtiff-dev libevent-dev clang libc++-dev libc++abi-dev -y

Install gstreamer bits

sudo apt install gstreamer1.0-tools libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev -y

Build libcamera with gstreamer support

cd libcamera
GST_PLUGIN_PATH=$(pwd)/build/src/gstreamer
CC=clang CXX=clang++ meson build -Dpipelines=uvcvideo,ipu3 -Dprefix=/usr
ninja -C build
sudo ninja -C build install

At the end of this, you should be able to see both cameras if you run qcam. Yes, they have odd names.

Get a camera working over a video loopback, to expose it to other applications

sudo apt install v4l2loopback-dkms -y
sudo modprobe v4l2loopback video_nr=42 card_label="virtualcam" exclusive_caps=1

(The “card_label” switch is the name the device will show up as in firefox and other applications. “virtualcam” was fine by me, but change it if you want something else. It’s not a physical device though, so calling it “frontcam” or “backcam” would be confusing. You select the actual camera you want to use later.)

Run it

Yes, at the moment, each time you want to use the camera, you need to run this command.


gst-launch-1.0 libcamerasrc camera-name='\\_SB_.PCI0.I2C2.CAMF' ! 
    video/x-raw,width=1280,height=720,framerate=30/1,format=NV12 
    ! videoconvert ! video/x-raw,format=YUY2 ! videoconvert ! 
    v4l2sink device=/dev/video42

This will use the front camera on a Surface Pro 6 and a SurfaceBook 2. If you want the rear camera, you’d need to change the camera-name switch to _SB_.PCI0.I2C2.CAMR.

On a Surface Go, the camera-name for the front camera is _SB_.PCI0.LNK1. The rear camera is _SB_.PCI0.LNK0.

Using an alias to make it a bit easier

Rather than using the full command each time, using a bash alias can make it a bit easier.

I created a shell script, and aliased that.

The shell script (called camera.sh) is:

#!/bin/bash

gst-launch-1.0 libcamerasrc camera-name='\\_SB_.PCI0.I2C2.CAMF' ! 
    video/x-raw,width=1280,height=720,framerate=30/1,format=NV12 
    ! videoconvert ! video/x-raw,format=YUY2 ! videoconvert ! 
    v4l2sink device=/dev/video42

I later tweaked this, so that I could use the same script and the same alias on multiple machines, with different camera names:

#!/bin/bash

HOSTNAME=$(cat /etc/hostname)

echo "This machine is $HOSTNAME"

if [ $HOSTNAME == "surfacego" ];
then
	CAMERANAME='\\_SB_.PCI0.LNK1'
else
	CAMERANAME='\\_SB_.PCI0.I2C2.CAMF'
fi

echo "Camera is $CAMERANAME"

gst-launch-1.0 libcamerasrc camera-name=$CAMERANAME ! 
    video/x-raw,width=1280,height=720,framerate=30/1,format=NV12 
    ! videoconvert ! video/x-raw,format=YUY2 ! videoconvert ! 
    v4l2sink device=/dev/video42

Update: there’s a bug in the blog engine, which means I’ve had to escape the backslashes in this post, to make them appear correctly in the rendered output. The camera name needs two backslashes before _SB, in case the blog system has removed them again.

In ~/.bashrc, I added:

alias startcam="/bin/bash /path/to/camera.sh"

Then, reload your ~/.bashrc config:

source ~/.bashrc

So now, by typing startcam, the camera starts.

Loading the kernel modules automatically

Note that loading a kernel module via modprobe lasts only for the current session - after a reboot, you’ll need to do it again. You could make it part of the bash script, but that would be a pain, as it requires superuser permission.

If you are going to use this regularly, it would make more sense to load the kernel modules automatically:

echo "v4l2loopback" | sudo tee -a /etc/modules-load.d/v4l2loopback.conf 

echo "options v4l2loopback video_nr=42 card_label="virtualcam" exclusive_caps=1" | sudo tee -a /etc/modprobe.d/v4l2loopback.conf

update-initramfs -c -k $(uname -r)

Thank you to the libcamera team

Thank you to everyone working on libcamera for making this possible.