1 { config, lib, pkgs, utils, ... }:
4 inherit (lib) attrValues concatStringsSep filterAttrs length listToAttrs literalExpression
5 makeSearchPathOutput mkEnableOption mkIf mkOption nameValuePair optionals types;
6 inherit (utils) escapeSystemdPath;
8 cfg = config.services.v4l2-relayd;
10 kernelPackages = config.boot.kernelPackages;
12 gst = (with pkgs.gst_all_1; [
19 instanceOpts = { name, ... }: {
21 enable = mkEnableOption "this v4l2-relayd instance";
27 The name of the instance.
31 cardLabel = mkOption {
34 The name the camera will show up as.
38 extraPackages = mkOption {
39 type = with types; listOf package;
42 Extra packages to add to {env}`GST_PLUGIN_PATH` for the instance.
50 The gstreamer-pipeline to use for the input-stream.
58 The video-format to read from input-stream.
63 type = types.ints.positive;
66 The width to read from input-stream.
71 type = types.ints.positive;
74 The height to read from input-stream.
78 framerate = mkOption {
79 type = types.ints.positive;
82 The framerate to read from input-stream.
92 The video-format to write to output-stream.
103 options.services.v4l2-relayd = {
105 instances = mkOption {
106 type = with types; attrsOf (submodule instanceOpts);
108 example = literalExpression ''
111 cardLabel = "Example card";
112 input.pipeline = "videotestsrc";
117 v4l2-relayd instances to be created.
126 mkInstanceService = instance: {
127 description = "Streaming relay for v4l2loopback using GStreamer";
129 after = [ "modprobe@v4l2loopback.service" "systemd-logind.service" ];
130 wantedBy = [ "multi-user.target" ];
135 PrivateNetwork = true;
141 GST_PLUGIN_PATH = makeSearchPathOutput "lib" "lib/gstreamer-1.0" (gst ++ instance.extraPackages);
142 V4L2_DEVICE_FILE = "/run/v4l2-relayd-${instance.name}/device";
147 appsrcOptions = concatStringsSep "," [
149 "format=${instance.input.format}"
150 "width=${toString instance.input.width}"
151 "height=${toString instance.input.height}"
152 "framerate=${toString instance.input.framerate}/1"
156 "appsrc name=appsrc ${appsrcOptions}"
158 ] ++ optionals (instance.input.format != instance.output.format) [
159 "video/x-raw,format=${instance.output.format}"
161 ] ++ [ "v4l2sink name=v4l2sink device=$(cat $V4L2_DEVICE_FILE)" ];
164 exec ${pkgs.v4l2-relayd}/bin/v4l2-relayd -i "${instance.input.pipeline}" -o "${concatStringsSep " ! " outputPipeline}"
168 mkdir -p $(dirname $V4L2_DEVICE_FILE)
169 ${kernelPackages.v4l2loopback.bin}/bin/v4l2loopback-ctl add -x 1 -n "${instance.cardLabel}" > $V4L2_DEVICE_FILE
173 ${kernelPackages.v4l2loopback.bin}/bin/v4l2loopback-ctl delete $(cat $V4L2_DEVICE_FILE)
174 rm -rf $(dirname $V4L2_DEVICE_FILE)
178 mkInstanceServices = instances: listToAttrs (map
180 nameValuePair "v4l2-relayd-${escapeSystemdPath instance.name}" (mkInstanceService instance)
184 enabledInstances = attrValues (filterAttrs (n: v: v.enable) cfg.instances);
189 boot = mkIf ((length enabledInstances) > 0) {
190 extraModulePackages = [ kernelPackages.v4l2loopback ];
191 kernelModules = [ "v4l2loopback" ];
194 systemd.services = mkInstanceServices enabledInstances;
198 meta.maintainers = with lib.maintainers; [ betaboon ];