リモートワークとかするときにzoomとかslackとかGoogle Meetとかでビデオ会議をするんですけど任意の画像をカメラ画像として差し込めると楽しいと思わないですか?
LinuxだとCanonとかの一眼レフとかをWebカメラとして認識させるのにv4l2を利用してやる方法が有るんですけど, v4l2を利用することで任意の画像をWebカメラの画像として配信することができます.

ざっくり言うと

  1. 標準出力に1フレームの情報としてjpegを吐き出す
  2. ffmpegでv4l2の仮想デバイスに吐き出す

簡単ですね.

sample.jpegというダミー画像を出力することを考えましょう.
まずはじめに仮想カメラを生成します.

# /dev/video20 として仮想カメラを作る
$ sudo modprobe v4l2loopback card_label="dummy_cam" video_nr=20 exclusive_caps=1

次にGoのプログラムを書く.
とりあえず main.go とかで用意する.

package main

import (
	"bytes"
	"image"
	"image/color"
	"image/jpeg"
	"os"
)

func main() {
	of, err := os.Open("sample.jpeg")
	if err != nil {
		panic(err)
	}

	jpg, err := jpeg.Decode(of)
	if err != nil {
		panic(err)
	}
    b := jpg.Bounds()
    
    // jpegをYUV420pに変換する
	converted := image.NewYCbCr(b, image.YCbCrSubsampleRatio420)

	for row := 0; row < b.Max.Y; row++ {
		for col := 0; col < b.Max.X; col++ {
			c := jpg.At(col, row)
			y := c.(color.YCbCr).Y
			cb := c.(color.YCbCr).Cb
			cr := c.(color.YCbCr).Cr

			converted.Y[converted.YOffset(col, row)] = y
			converted.Cb[converted.COffset(col, row)] = cb
			converted.Cr[converted.COffset(col, row)] = cr
		}
	}

	buf := new(bytes.Buffer)
	if err := jpeg.Encode(buf, converted, nil); err != nil {
		panic(err)
	}

	bin := buf.Bytes()

	for {
		os.Stdout.Write(bin)
		os.Stdout.Sync()
	}
}

プログラムが用意できたらffmpegに標準出力を食わせる.

$ go run main.go | ffmpeg -i - -vcodec rawvideo -pix_fmt yuv420p -f v4l2 /dev/video20

これで /dev/video20 からjpeg画像が出力される.
応用するとOpenCVなどで画像を編集して出力することもできる.