Faking a webcam video session by streaming from ffmpeg

6 minute read Published: 2021-03-04

Recently I had to test a videoservice website for a server to server integration. Since I did not want to stream myself while doing tests, I have investigated how to leverage the great flexibility of Linux tooling. Let's review what I've learned in order to stream an arbitrary video on the browser.

§ The components

Let's start with some basic facts:

The simplified workflow is:

Let's start! First we need to create a virtual video device camera.

Install the kernel module for v4l2loopback, the kernel interface for a video loopback (i.e. fake) video device:

$ sudo apt install v4l2loopback-dkms

If for any reason you can't install a package, compile it yourself:

$ git clone https://github.com/umlaeute/v4l2loopback
$ make && sudo make install && sudo depmod -a

Create a virtual video device

$ ls /dev/video*
video0  video1
$ sudo modprobe v4l2loopback
$ ls /dev/video*
video0  video1  video2

Ok, we now have a fake camera (/dev/video2)!

§ Feed a virtual cam with a video stream

Open Firefox on any test website that allow using the camera and microphone, example a videocall website or simply this test page.

Now follow this sequence:

  1. Play the video with ffmpeg and redirect the video stream to the v4l2 loopback device:

    ffmpeg -re -i test.avi -f v4l2 /dev/video2

  2. Have the website ask for permissions to use camera and microphone: you should now see an additional "dummy device", which is the stream being played by ffmpeg.

If you start recording, you should see that the video stream comes from ffmpeg, not the webcam. The audio stream will be discarded by ffmpeg so the website will record a video mute.

§ Feed a virtual cam and a virtual mic with both a/v streams

To also passthrough the audio, there's a bit more legwork.

Let's have a look in detail to the recording device on our workstation. Each physical audio card has a sound and a monitor device. We want to use the latter to grab the audio. Take note of the alsa.card_name, it will be used later. Note that even if the name says "ALSA" it's also the name of the Pulseaudio device (go figure).

$ pactl list sources
Source #0
	Name: alsa_output.pci-0000_00_1f.3.analog-stereo.monitor
	Description: Monitor of Built-in Audio Analog Stereo
	Properties:
		device.description = "Monitor of Built-in Audio Analog Stereo"
		device.class = "monitor"
		alsa.card_name = "HDA Intel PCH"
		alsa.long_card_name = "HDA Intel PCH at 0xe8248000 irq 167"
		alsa.driver_name = "snd_hda_intel"
		sysfs.path = "/devices/pci0000:00/0000:00:1f.3/sound/card0"
Source #1
	Name: alsa_input.pci-0000_00_1f.3.analog-stereo
	Description: Built-in Audio Analog Stereo
	Properties:
		device.description = "Built-in Audio Analog Stereo"
		device.class = "sound"
		alsa.card_name = "HDA Intel PCH"
		alsa.long_card_name = "HDA Intel PCH at 0xe8248000 irq 167"
		alsa.driver_name = "snd_hda_intel"
		sysfs.path = "/devices/pci0000:00/0000:00:1f.3/sound/card0"

In the previous output, we note that Source #0 has device.class = "monitor" and its name is "HDA Intel PCH".

Now we have to tell ffmpeg to split the audio and video streams and send each one to the proper virtual device. Let's identify the streams in the file we want to play:

$ ffprobe test.avi 2>&1 | grep Stream
    Stream #0:0(eng): Audio: aac (LC) (mp4a / 0x6134706D) ...
    Stream #0:1(eng): Video: h264 (Main) (avc1 / 0x31637661) ...

We identify that #0:0 is the audio stream and #0:1 is the video stream.

Let's play the video and instruct ffmpeg according to the information we have:

ffmpeg -i test.mp4 \

  # send the stream "0:0" to a device with format "v4l2" on "/dev/video2"
  -map 0:0 -f v4l2 /dev/video2 \

  # send the stream "0:1" to a device with format "pulse" called "HDA Intel PCH"
  -map 0:1 -f pulse "HDA Intel PCH"

Now you should see that firefox is recording both from the virtual cam and the virtual mic.

To verify this you can open pavucontrol, go to the "Recording" tab and observe that Firefox is using the monitor device, you should see the volume bar moving:

Now reload the website to reset permissions and have it ask them again. This time when you select the monitor device you'll be plugged to the ffmpeg stream running:

And you're set, you'll be recording both audio and video from ffmpeg!

Notes:

§ Bonus: Renaming the devices

Unrelated to this topic, but I'll throw this in for future use. If you don't like the device names, you can easily change them with:

pacmd update-sink-proplist YOURDEVICE device.description=NEWDEVICENAME
(if it's a sink, i.e. an output device)

pacmd update-source-proplist YOURDEVICE device.description=NEWDEVICENAME
(if it's an input device)

Example:

pacmd update-sink-proplist \
    alsa_output.pci-0000_00_1f.3.analog-stereo.monitor \
    device.description=LoopbackDevice

To reset these settings, kill pulseaudio with pulseaudio -k then start pavucontrol to force pulseaudio to restart again. I didn't investigate how to persiste them.

§ References

Here's some references. As usual, figuring out everything involves a bit of searching and assembling various pieces from different places: