How to use your DSLR from 2008 as a webcam in 2022 (NixOS)

Canon eos rebel xs being used as a webcamCanon eos rebel xs being used as a webcam

This year after largely abandoning my macbook in favour of a nixos machine, I started getting requests to "turn my camera on" when video calling people. This was a problem because I didn't actually have a webcam. I thought about buying one but then I realised that I had a perfectly good Canon EOS rebel XS DSLR circa 2008 lying around on my shelf. This camera has a mini-USB port, so naturally I pondered: DSLR + mini-USB + desktop PC = possible webcam ?

But there is just one problem. My Canon EOS rebel XS isn't actually capable of recording video. It can take some nice pictures but that's about it. So that's the end of that then.

Or is it?

There happens to be some amazing open source software called gphoto2. Once installed it will allow you to control an array of different supported cameras from your computer (find out if yours is supported with gphoto2 --list-cameras). This includes taking photos and videos. After installing, try taking a picture with it like so: gphoto2 --capture-image-and-download. You should hear the shutter activate and the image will be saved to your current working directory.

Despite the aforementioned lack of video functionality on my camera, I decided to try gphoto2 --capture-movie anyway. Somehow, although my camera does not support video natively, this tool still manages to spit out an mjpeg file (For my camera I needed to put it in "live-view" mode before gphoto2 could record video. This consisted of putting it on portrait mode and then pressing the "set" button so that the viewfinder is off and the screen is displaying an image). Unfortunately though this is not enough to be able to use it as a webcam. It still needs to get assigned a video device such as /dev/video**.

How do?

First of all if you haven't already, you're gonna want to grab gphoto2 and ffmpeg.

And maybe mpv also.

NIX
# configuration.nix
...
environment.systemPackages = with pkgs; [
ffmpeg
gphoto2
mpv
...

To create the virtual video device we will need to make use of the v4l2 Linux kernel module. It can be installed by adding to the extra module packages in configuration.nix.

NIX
# configuration.nix
...
boot.extraModulePackages = with config.boot.kernelPackages;
[ v4l2loopback.out ];
boot.kernelModules = [
"v4l2loopback"
];
boot.extraModprobeConfig = ''
options v4l2loopback exclusive_caps=1 card_label="Virtual Camera"
'';
...

You will now need to run sudo nixos-rebuild switch and also reboot your computer since we have made some changes to the kernel.

Now try running this command:

SHELL
gphoto2 --stdout --capture-movie |
ffmpeg -i - -vcodec rawvideo -pix_fmt yuv420p -f v4l2 /dev/video0

You should see output like this:

ffmpeg version 4.4.1 Copyright (c) 2000-2021 the FFmpeg developers
built with gcc 11.3.0 (GCC)
configuration: --disable-static ...
libavutil 56. 70.100 / 56. 70.100
libavcodec 58.134.100 / 58.134.100
libavformat 58. 76.100 / 58. 76.100
libavdevice 58. 13.100 / 58. 13.100
libavfilter 7.110.100 / 7.110.100
libavresample 4. 0. 0 / 4. 0. 0
libswscale 5. 9.100 / 5. 9.100
libswresample 3. 9.100 / 3. 9.100
libpostproc 55. 9.100 / 55. 9.100
Capturing preview frames as movie to 'stdout'. Press Ctrl-C to abort.
[mjpeg @ 0x1dd0380] Format mjpeg detected only with low score of 25, misdetection possible!
Input #0, mjpeg, from 'pipe:':
Duration: N/A, bitrate: N/A
Stream #0:0: Video: mjpeg (Baseline), yuvj422p(pc, bt470bg/unknown/unknown), 768x512 ...
Stream mapping:
Stream #0:0 -> #0:0 (mjpeg (native) -> rawvideo (native))
[swscaler @ 0x1e27340] deprecated pixel format used, make sure you did set range correctly
Output #0, video4linux2,v4l2, to '/dev/video0':
Metadata:
encoder : Lavf58.76.100
Stream #0:0: Video: rawvideo (I420 / 0x30323449) ...
Metadata:
encoder : Lavc58.134.100 rawvideo
frame= 289 fps= 23 q=-0.0 size=N/A time=00:00:11.56 bitrate=N/A speed=0.907x

Now try this command:

SHELL
mpv av://v4l2:/dev/video0 --profile=low-latency --untimed

You should now be able to see the video feed from your webcam.

Streaming a live feed from the webcamStreaming a live feed from the webcam

What about starting the webcam automatically?

It is a bit annoying to have to execute a command every time we want to use our webcam. Luckily there are ways for this command to be automatically run on startup.

I have decided to implement this webcam startup command as a systemd service.

1
This line causes the service to be started before you log in
NIX
# configuration.nix
...
systemd.services.webcam = {
enable = true;
script = ''
${pkgs.gphoto2}/bin/gphoto2 --stdout --capture-movie |
${pkgs.ffmpeg}/bin/ffmpeg -i - \
-vcodec rawvideo -pix_fmt yuv420p -f v4l2 /dev/video0
'';
1wantedBy = [ "multi-user.target" ];
};
...
1
This line causes the service to be started before you log in

Now if you do a quick sudo nixos-rebuild switch and reboot your computer you should find that the webcam service is running.

To check for any problems we can use systemctl status webcam which will tell us the last time the service was run as well as log of its last output. Handy for debugging.

Are we done?

Its very tempting to stop here. However considering the current global crises it may be pertinent to wonder whether it is necessary to have the webcam on all the time. It strikes me as sub-optimal for 3 obvious reasons:

  1. Its a waste of electricity.
  2. There are privacy concerns associated with this kind of thing.
  3. I have to keep changing the battery (solved by this)

My camera has a lens cap, so to be honest the second point does not really bother me. I can always put the lens cap on when I am not using the webcam to make sure certain government agencies aren't being entertained at my expense. However, leaving a big power hungry DSLR camera on 24/7 certainly is not doing anything for my electricity bill. That's not to mention the CPU overhead required for decoding the video... which upon measurement with htop looks to be around 10%. Not insignificant especially when considering that I'm not exactly running a podcast here, I don't have that many video calls in a day.

The ideal scenario:

  • I leave my camera plugged in to my computer all the time but switched off
  • When I want to use the webcam I switch on the camera with its power button
  • My computer then automatically detects the camera and starts the systemd service
  • After finishing with the webcam I switch it off again

To achieve this we need to make use of a custom udev rule. A udev rule is something that tells your computer to perform a certain task when it discovers that a device has become available. This could be an external hard drive or even non-usb devices. In our case we need it to recognise the camera through its usb connection.

We need to specify what command is to be run when the udev rule is triggered. For that I am creating a derivation (nix package) that simply restarts the systemd service. You could also add logging to this for debugging purposes.

1
Convert script to nix derivation.
NIX
# start-webcam.nix
with import <nixpkgs> { };
1writeShellScriptBin"start-webcam" ''
systemctl restart webcam
# debugging example
# echo "hello" &> /home/tom/myfile.txt
# If myfile.txt gets created then we know the udev rule has triggered properly
''
1
Convert script to nix derivation.

Now to actually define the udev rule. First of all we need to find out the device and vendor id of the camera. This is done using the lsusb command. Since I don't see myself using this particularly often I will install it temporarily using nix-shell.

This can be done like so: nix-shell -p usbutils

Then running lsusb we get the following output:

SHELL
[nix-shell:~/environment]$ lsusb
...
Bus 002 Device 008: ID 04a9:317b Canon, Inc. Canon Digital Camera
...

We can see from this output that the vendor ID is 04a9 and the device ID is 317b.

We can now create the udev rule.

1
Import the derivation we made earlier.
NIX
# configuration.nix
...
let
startWebcam = import 1./start-webcam.nix;
...
services.udev.extraRules = ''
ACTION=="add", \
SUBSYSTEM=="usb", \
ATTR{idVendor}=="04a9", \
ATTR{idProduct}=="317b", \
RUN+="${startWebcam}/bin/start-webcam"
'';
...
1
Import the derivation we made earlier.

We just need to remove the wantedBy = [ "multi-user.target" ]; line in our systemd service. If we leave this in then the service will start automatically when we next reboot whether the camera is switched on or not.

One more sudo nixos-rebuild switch and we are finished!

Thanks for reading this far. I hope this article has made you think twice before chucking some of your old tech.