diff --git a/machines/storage/s0/frigate.nix b/machines/storage/s0/frigate.nix index 2d164df..3e5e5d0 100644 --- a/machines/storage/s0/frigate.nix +++ b/machines/storage/s0/frigate.nix @@ -3,105 +3,153 @@ let frigateHostname = "frigate.s0.neet.dev"; - mkEsp32Cam = address: { - ffmpeg = { - input_args = ""; - inputs = [{ - path = "http://${address}:8080"; - roles = [ "detect" "record" ]; - }]; - - output_args.record = "-f segment -pix_fmt yuv420p -segment_time 10 -segment_format mp4 -reset_timestamps 1 -strftime 1 -c:v libx264 -preset ultrafast -an "; - }; - detect = { - enabled = true; - width = 800; - height = 600; - fps = 10; - }; - objects = { - track = [ "person" ]; - }; + mkGo2RtcStream = name: url: withAudio: { + ${name} = [ + url + "ffmpeg:${name}#video=copy${if withAudio then "#audio=copy" else ""}" + ]; }; - mkDahuaCam = address: { - ffmpeg = { - inputs = [ - { - path = "rtsp://admin:{FRIGATE_RTSP_PASSWORD}@${address}/cam/realmonitor?channel=1&subtype=0"; - roles = [ "record" ]; - } - { - path = "rtsp://admin:{FRIGATE_RTSP_PASSWORD}@${address}/cam/realmonitor?channel=1&subtype=1"; - roles = [ "detect" ]; - } - ]; - }; - detect.enabled = true; - objects = { - track = [ "person" "dog" ]; - }; - }; -in -{ - networking.firewall.allowedTCPPorts = [ - # 1883 # mqtt - ]; - - services.frigate = { - enable = true; - hostname = frigateHostname; - settings = { - mqtt = { - enabled = true; - host = "localhost:1883"; - }; - rtmp.enabled = false; - snapshots = { - enabled = true; - bounding_box = true; - }; - record = { - enabled = true; - # sync_recordings = true; # detect if recordings were deleted outside of frigate - retain = { - days = 2; # Keep video for 2 days - mode = "motion"; - }; - events = { - retain = { - default = 10; # Keep video with detections for 10 days - mode = "motion"; - # mode = "active_objects"; + # Assumes camera is set to output: + # - rtsp + # - H.264 + AAC + # - a downscaled substream for detection + mkCamera = name: primaryUrl: detectUrl: { + # Reference https://docs.frigate.video/configuration/reference/ + services.frigate.settings = { + cameras.${name} = { + ffmpeg = { + # Camera feeds are relayed through go2rtc + inputs = [ + { + path = "rtsp://127.0.0.1:8554/${name}"; + # input_args = "preset-rtsp-restream"; + input_args = "preset-rtsp-restream-low-latency"; + roles = [ "record" ]; + } + { + path = detectUrl; + roles = [ "detect" ]; + } + ]; + output_args = { + record = "preset-record-generic-audio-copy"; }; }; }; - cameras = { - dahlia-cam = mkEsp32Cam "dahlia-cam.lan"; - dog-cam = mkDahuaCam "192.168.10.31"; - }; - # ffmpeg = { - # hwaccel_args = "preset-vaapi"; - # }; - detectors.coral = { - type = "edgetpu"; - device = "pci"; + }; + services.go2rtc.settings.streams = lib.mkMerge [ + (mkGo2RtcStream name primaryUrl false) + + # Sadly having the detection stream go through go2rpc too makes the stream unreadable by frigate for some reason. + # It might need to be re-encoded to work. But I am not interested in wasting the processing power if only frigate + # need the detection stream anyway. So just let frigate grab the stream directly since it works. + # (mkGo2RtcStream detectName detectUrl false) + ]; + }; + + mkDahuaCamera = name: address: + let + # go2rtc and frigate have a slightly different syntax for inserting env vars. So the URLs are not interchangable :( + # - go2rtc: ${VAR} + # - frigate: {VAR} + primaryUrl = "rtsp://admin:\${FRIGATE_RTSP_PASSWORD}@${address}/cam/realmonitor?channel=1&subtype=0"; + detectUrl = "rtsp://admin:{FRIGATE_RTSP_PASSWORD}@${address}/cam/realmonitor?channel=1&subtype=1"; + in + mkCamera name primaryUrl detectUrl; + + mkEsp32Camera = name: address: { + services.frigate.settings.cameras.${name} = { + ffmpeg = { + input_args = ""; + inputs = [{ + path = "http://${address}:8080"; + roles = [ "detect" "record" ]; + }]; + + output_args.record = "-f segment -pix_fmt yuv420p -segment_time 10 -segment_format mp4 -reset_timestamps 1 -strftime 1 -c:v libx264 -preset ultrafast -an "; }; }; }; +in +lib.mkMerge [ + (mkDahuaCamera "dog-cam" "192.168.10.31") + # (mkEsp32Camera "dahlia-cam" "dahlia-cam.lan") + { + services.frigate = { + enable = true; + hostname = frigateHostname; + settings = { + mqtt = { + enabled = true; + host = "localhost:1883"; + }; + rtmp.enabled = false; + snapshots = { + enabled = true; + bounding_box = true; + }; + record = { + enabled = true; + # sync_recordings = true; # detect if recordings were deleted outside of frigate (expensive) + retain = { + days = 2; # Keep video for 2 days + mode = "motion"; + }; + events = { + retain = { + default = 10; # Keep video with detections for 10 days + mode = "motion"; + # mode = "active_objects"; + }; + }; + }; + # Make frigate aware of the go2rtc streams + go2rtc.streams = config.services.go2rtc.settings.streams; + detect.enabled = true; + objects = { + track = [ "person" "dog" ]; + }; + }; + }; - # Pass in env file with secrets to frigate - systemd.services.frigate.serviceConfig.EnvironmentFile = "/run/agenix/frigate-credentials"; - age.secrets.frigate-credentials.file = ../../../secrets/frigate-credentials.age; + services.go2rtc = { + enable = true; + settings = { + rtsp.listen = ":8554"; + webrtc.listen = ":8555"; + }; + }; - # AMD GPU for vaapi - systemd.services.frigate.environment.LIBVA_DRIVER_NAME = "radeonsi"; + # Pass in env file with secrets to frigate/go2rtc + systemd.services.frigate.serviceConfig.EnvironmentFile = "/run/agenix/frigate-credentials"; + systemd.services.go2rtc.serviceConfig.EnvironmentFile = "/run/agenix/frigate-credentials"; + age.secrets.frigate-credentials.file = ../../../secrets/frigate-credentials.age; + } + { + # hardware encode/decode with amdgpu vaapi + systemd.services.frigate = { + environment.LIBVA_DRIVER_NAME = "radeonsi"; + serviceConfig = { + SupplementaryGroups = [ "render" "video" ]; # for access to dev/dri/* + AmbientCapabilities = "CAP_PERFMON"; + }; + }; + services.frigate.settings.ffmpeg.hwaccel_args = "preset-vaapi"; + } + { + # Coral TPU for frigate + services.udev.packages = [ pkgs.libedgetpu ]; + users.groups.apex = { }; + systemd.services.frigate.environment.LD_LIBRARY_PATH = "${pkgs.libedgetpu}/lib"; + systemd.services.frigate.serviceConfig.SupplementaryGroups = [ "apex" ]; - # Coral TPU for frigate - services.udev.packages = [ pkgs.libedgetpu ]; - users.groups.apex = { }; - systemd.services.frigate.environment.LD_LIBRARY_PATH = "${pkgs.libedgetpu}/lib"; - systemd.services.frigate.serviceConfig.SupplementaryGroups = "apex"; - # Coral PCIe driver - kernel.enableGasketKernelModule = true; -} + # Coral PCIe driver + kernel.enableGasketKernelModule = true; + + services.frigate.settings.detectors.coral = { + type = "edgetpu"; + device = "pci"; + }; + } +]