From f6c0620f8efb439d1d1cd55660471c81e68be7ce Mon Sep 17 00:00:00 2001 From: Jordan Holt Date: Sat, 21 Jun 2025 23:41:13 +0100 Subject: [PATCH] hosts/skycam: working stream with go2rtc --- flake.nix | 1 + hosts/skycam/default.nix | 89 +++---------------- hosts/skycam/hardware-configuration.nix | 61 ++++++++++++- .../libcamera-rpi-ipa-priv-key.pem | 28 ++++++ pkgs/libcamera-rpi/package.nix | 66 ++++++++++++++ .../patches/libcamera-installed.patch | 53 +++++++++++ .../patches/libcamera-no-timeout.patch | 29 ++++++ pkgs/rpicam-apps/package.nix | 68 ++++++++++++++ 8 files changed, 314 insertions(+), 81 deletions(-) create mode 100644 pkgs/libcamera-rpi/libcamera-rpi-ipa-priv-key.pem create mode 100644 pkgs/libcamera-rpi/package.nix create mode 100644 pkgs/libcamera-rpi/patches/libcamera-installed.patch create mode 100644 pkgs/libcamera-rpi/patches/libcamera-no-timeout.patch create mode 100644 pkgs/rpicam-apps/package.nix diff --git a/flake.nix b/flake.nix index a0b9891..659df55 100644 --- a/flake.nix +++ b/flake.nix @@ -136,6 +136,7 @@ nixfmt-rfc-style.enable = true; trim-trailing-whitespace.enable = true; }; + excludes = [ "pkgs/libcamera-rpi/libcamera-rpi-ipa-priv-key.pem" ]; }; })); diff --git a/hosts/skycam/default.nix b/hosts/skycam/default.nix index d552216..9f4b4a9 100644 --- a/hosts/skycam/default.nix +++ b/hosts/skycam/default.nix @@ -1,73 +1,16 @@ { - inputs, - config, pkgs, ... }: { imports = [ - inputs.nixos-hardware.nixosModules.raspberry-pi-4 ./hardware-configuration.nix ../server.nix ]; nixpkgs.hostPlatform = "aarch64-linux"; - hardware = { - raspberry-pi."4" = { - apply-overlays-dtmerge.enable = true; - audio.enable = false; - xhci.enable = false; - }; - deviceTree = { - enable = true; - filter = "*rpi-4-*.dtb"; - # From https://github.com/Electrostasy/dots/blob/3b81723feece67610a252ce754912f6769f0cd34/hosts/phobos/klipper.nix#L43-L65 - overlays = - let - mkCompatibleDtsFile = - dtbo: - let - drv = - pkgs.runCommand "fix-dts" - { - nativeBuildInputs = with pkgs; [ - dtc - gnused - ]; - } - '' - mkdir "$out" - dtc -I dtb -O dts ${dtbo} | sed -e 's/bcm2835/bcm2711/' > $out/overlay.dts - ''; - in - "${drv}/overlay.dts"; - - inherit (config.boot.kernelPackages) kernel; - in - [ - { - name = "imx708.dtbo"; - dtsFile = mkCompatibleDtsFile "${kernel}/dtbs/overlays/imx708.dtbo"; - } - { - name = "vc4-kms-v3d-pi4.dtbo"; - dtsFile = mkCompatibleDtsFile "${kernel}/dtbs/overlays/vc4-kms-v3d-pi4.dtbo"; - } - ]; - }; - firmware = with pkgs; [ - firmwareLinuxNonfree - ]; - }; - - services.udev.extraRules = '' - SUBSYSTEM=="rpivid-*", GROUP="video", MODE="0660" - KERNEL=="vcsm-cma", GROUP="video", MODE="0660" - SUBSYSTEM=="dma_heap", GROUP="video", MODE="0660" - ''; - networking = { hostId = "731d1660"; firewall = { @@ -77,27 +20,16 @@ }; }; - systemd.services.ustreamer = { - enable = true; - description = "uStreamer service"; - unitConfig = { - Type = "simple"; - ConditionPathExists = "/sys/bus/i2c/drivers/imx708/10-001a/video4linux"; + services.go2rtc = + let + rpicam-vid = "${pkgs.rpicam-apps}/bin/rpicam-vid"; + in + { + enable = true; + settings = { + streams.rpicam = "exec:${rpicam-vid} -v1 -t0 -o- --inline --width=4608 --height=2592 --framerate=14 --codec mjpeg --quality 90 --denoise=off --sharpness 1.1"; + }; }; - serviceConfig = { - ExecStart = '' - ${pkgs.libcamera}/bin/libcamerify ${pkgs.unstable.ustreamer}/bin/ustreamer \ - --host=0.0.0.0 \ - --resolution=4608x2592 - ''; - DynamicUser = "yes"; - SupplementaryGroups = [ "video" ]; - Restart = "always"; - RestartSec = 10; - }; - wantedBy = [ "network-online.target" ]; - confinement.mode = "chroot-only"; - }; environment.systemPackages = with pkgs; [ git @@ -105,8 +37,7 @@ libcamera libraspberrypi raspberrypi-eeprom - v4l-utils - unstable.ustreamer + rpicam-apps ]; system.stateVersion = "24.05"; diff --git a/hosts/skycam/hardware-configuration.nix b/hosts/skycam/hardware-configuration.nix index 423a739..ebe6d62 100644 --- a/hosts/skycam/hardware-configuration.nix +++ b/hosts/skycam/hardware-configuration.nix @@ -1,18 +1,20 @@ { + inputs, + config, lib, + pkgs, modulesPath, ... }: { imports = [ + inputs.nixos-hardware.nixosModules.raspberry-pi-4 (modulesPath + "/installer/sd-card/sd-image-aarch64.nix") ]; boot = { - kernelModules = [ "bcm2835-v4l2" ]; kernelParams = [ - "cma=512M" "panic=0" ]; supportedFilesystems = lib.mkForce [ @@ -23,6 +25,61 @@ tmp.cleanOnBoot = false; }; + hardware = { + raspberry-pi."4" = { + apply-overlays-dtmerge.enable = true; + audio.enable = false; + xhci.enable = false; + }; + deviceTree = { + enable = true; + filter = "*rpi-4-*.dtb"; + # From https://github.com/Electrostasy/dots/blob/3b81723feece67610a252ce754912f6769f0cd34/hosts/phobos/klipper.nix#L43-L65 + overlays = + let + mkCompatibleDtsFile = + dtbo: + let + drv = + pkgs.runCommand "fix-dts" + { + nativeBuildInputs = with pkgs; [ + dtc + gnused + ]; + } + '' + mkdir "$out" + dtc -I dtb -O dts ${dtbo} | sed -e 's/bcm2835/bcm2711/' > $out/overlay.dts + ''; + in + "${drv}/overlay.dts"; + + inherit (config.boot.kernelPackages) kernel; + in + [ + { + name = "imx708.dtbo"; + dtsFile = mkCompatibleDtsFile "${kernel}/dtbs/overlays/imx708.dtbo"; + } + { + name = "vc4-kms-v3d-pi4.dtbo"; + dtsFile = mkCompatibleDtsFile "${kernel}/dtbs/overlays/vc4-kms-v3d-pi4.dtbo"; + } + ]; + }; + firmware = with pkgs; [ + firmwareLinuxNonfree + ]; + }; + + services.udev.extraRules = '' + SUBSYSTEM=="rpivid-*", GROUP="video", MODE="0660" + KERNEL=="vcsm-cma", GROUP="video", MODE="0660" + SUBSYSTEM=="dma_heap", GROUP="video", MODE="0660" + SUBSYSTEM=="dma_heap", KERNEL=="linux,cma", SYMLINK+="dma_heap/vidbuf_cached", OPTIONS+="link_priority=-50" + ''; + nixpkgs.overlays = [ (final: super: { makeModulesClosure = x: super.makeModulesClosure (x // { allowMissing = true; }); diff --git a/pkgs/libcamera-rpi/libcamera-rpi-ipa-priv-key.pem b/pkgs/libcamera-rpi/libcamera-rpi-ipa-priv-key.pem new file mode 100644 index 0000000..b5b5b17 --- /dev/null +++ b/pkgs/libcamera-rpi/libcamera-rpi-ipa-priv-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCocmtyzPPjv+52 +JiZrpZFfaZ0eeUgugc8gV+0+2Q9GEkl/xxqjiDVg31gBO3iwQov2NmGuPbXr+vwZ +QcUqNQakmmdi22tBaTtd6hMuhu9OfbP8sIFaf0dToZRHkPgf63+WCF6w0O9enEz4 +zjW3kPa1eVRVekiYCXGML/VhN+h5WwWouNWgEOw5JH39ZuGmhsGN5XekkHtyMkwq +Vr+JodoSizhYs9VBYNA1J4PlyiS4BYr4pLiLffzPwRjcSS777x33g+nWNr1lsFxB +nDoVvVnq0E7fiXxlmCtAr/7dv0Ug5ixuNfZ9yoT0f+mfUiG/anmfodHujIm2Db37 +jvmfxaq1AgMBAAECggEAFhJKBHSY92xod0g37A55fiZFTV8oZ1mgdXU386522yBd +y5Wf5rIcBmm1axHrFjNeCgClq3JQEk/kdP3Ccy2YBXzq04/7HYrHmd5oLYZGOINt +kExjYqN/SdTH7FmxPWN66AKIP8RcvQmfZ1GDxd4DiZNQitO3S96e53bIQPkVp8Lg +GfK6LQCdOGimD00wvRoeqbV0PWGGVMfx+KvD5hxKYolyi/hNUxToD28qCAoMlMTi +yL+17q3nIYZvUmL0k7d64U+lXF8ov3cVXNJzAzFi41MXZ2Xqk3Lj+IhNweUhlOyn +fTo8QntNlirNL/XmtJ+5mPbGufE/6zsSNOf2Cyz2aQKBgQDio/tA3tFBzOz31hox +gW6NKarhp7e5R3XHQjZPmQXKq2lGCTBN+LzwCLYDa+ZWkS+cel/xSbkUFl0dopCu +7uGrSvmVAv+l1k879WHsYmLlDjJSa8WmDtVQ0SJr70X9UJmD2BivWnTnzrpZFu2A +Nv57gvebJTI4tLfAAyIfbg8gOQKBgQC+RJRv8/jVha/4sPonQYvpH0scS0Xzwca6 +xd23e+vULBpk7IVzMbVGJEDdfWXVJeAO++FSQcgTJA38nfYm2XRPZAProliLaW8o +XVhhhWbXP7Jc8BmL5zyfDaLOXNFBX2kfr/oKeOoQ+0dRDlWKlarw1SxC+RT6i2qQ +YETgXHKmXQKBgGk8mWsqy2HRZOtDqE/6eLnlciprtVy7+M14Sj21oUHVTAGwPJTH +/fs7IEEAdikWK1RuYmRoxh60r7IWDTadR35BRxjRFqILnCkMLNcVbDRN3kH1NwZ/ +dr+bDG+v4ADazx2wVu39g7Erhc3eXpOddZcmXhDVObeo+nWXPt33PeDJAoGBAJ4v ++FVnuo8Tee1Cfogat87W5KSedIcnqSjpjt+Y2MXq8PrNplnSjwrE42UCd6KRvcnX +Ykr4Q/ad+D75uYgtLMVAuv2yWPl3bCJcETnrJkh5PbqFKEgntT/rn1sA0j0OrSDa +NwFz6+64a1+ZkkcJDjjykr0Px4BSXwOv9jOuyOdFAoGADZEADOLX5y4utxboe1M0 +UnaFKGEDE6H8qdRJQ9bSvEwJI142al02CvnvqvP4cpd8rKOCRs9nSXFJFXCedTLy +ojSVfjTyJMTVJxab/c/Qugkxb/TqGfEnZF2yoTsfPYp2pXRd6DvyKlDQzlSOj933 +FrqeSe1QKapuPRsujVwLZDU= +-----END PRIVATE KEY----- diff --git a/pkgs/libcamera-rpi/package.nix b/pkgs/libcamera-rpi/package.nix new file mode 100644 index 0000000..8a89f8d --- /dev/null +++ b/pkgs/libcamera-rpi/package.nix @@ -0,0 +1,66 @@ +{ + lib, + fetchFromGitHub, + libcamera, + boost, + nlohmann_json, + python3Packages, + git, + cacert, + meson, +}: +libcamera.overrideAttrs (old: { + buildInputs = old.buildInputs ++ [ + boost + nlohmann_json + ]; + nativeBuildInputs = old.nativeBuildInputs ++ [ python3Packages.pybind11 ]; + + BOOST_INCLUDEDIR = "${lib.getDev boost}/include"; + BOOST_LIBRARYDIR = "${lib.getLib boost}/lib"; + + patches = [ + ./patches/libcamera-installed.patch + ./patches/libcamera-no-timeout.patch + ]; + + postPatch = + old.postPatch + + '' + patchShebangs src/py/libcamera + ''; + + preBuild = '' + ninja src/ipa-priv-key.pem + install -D ${./libcamera-rpi-ipa-priv-key.pem} src/ipa-priv-key.pem + ''; + + mesonFlags = old.mesonFlags ++ [ + "--buildtype=release" + "-Dcam=disabled" + "-Dgstreamer=disabled" + "-Dipas=rpi/vc4,rpi/pisp" + "-Dpipelines=rpi/vc4,rpi/pisp" + "-Dtest=false" + ]; + + src = fetchFromGitHub { + owner = "raspberrypi"; + repo = "libcamera"; + rev = "d83ff0a4ae4503bc56b7ed48cd142c3dd423ad3b"; + sha256 = "sha256-VP0s1jOON9J3gn81aiemsChvGeqx0PPivQF5rmSga6M="; + + nativeBuildInputs = [ git ]; + + postFetch = '' + cd "$out" + + export NIX_SSL_CERT_FILE=${cacert}/etc/ssl/certs/ca-bundle.crt + + ${lib.getExe meson} subprojects download \ + libpisp + + find subprojects -type d -name .git -prune -execdir rm -r {} + + ''; + }; +}) diff --git a/pkgs/libcamera-rpi/patches/libcamera-installed.patch b/pkgs/libcamera-rpi/patches/libcamera-installed.patch new file mode 100644 index 0000000..bfa0057 --- /dev/null +++ b/pkgs/libcamera-rpi/patches/libcamera-installed.patch @@ -0,0 +1,53 @@ +From e65bbb6e263d99212cd29a32d89e4c45d0c05353 Mon Sep 17 00:00:00 2001 +From: Jordan Holt +Date: Sat, 21 Jun 2025 18:38:38 +0100 +Subject: [PATCH] libcamera installed + +--- + src/libcamera/source_paths.cpp | 9 --------- + src/py/libcamera/meson.build | 4 ++-- + 2 files changed, 2 insertions(+), 11 deletions(-) + +diff --git a/src/libcamera/source_paths.cpp b/src/libcamera/source_paths.cpp +index 1af5386a..3fc7d044 100644 +--- a/src/libcamera/source_paths.cpp ++++ b/src/libcamera/source_paths.cpp +@@ -39,15 +39,6 @@ namespace { + */ + bool isLibcameraInstalled() + { +- /* +- * DT_RUNPATH (DT_RPATH when the linker uses old dtags) is removed on +- * install. +- */ +- for (const ElfW(Dyn) *dyn = _DYNAMIC; dyn->d_tag != DT_NULL; ++dyn) { +- if (dyn->d_tag == DT_RUNPATH || dyn->d_tag == DT_RPATH) +- return false; +- } +- + return true; + } + +diff --git a/src/py/libcamera/meson.build b/src/py/libcamera/meson.build +index 596a203c..c0b1db59 100644 +--- a/src/py/libcamera/meson.build ++++ b/src/py/libcamera/meson.build +@@ -34,14 +34,14 @@ gen_py_controls = files('gen-py-controls.py') + pycamera_sources += custom_target('py_gen_controls', + input : controls_files, + output : ['py_controls_generated.cpp'], +- command : [gen_py_controls, '--mode', 'controls', '-o', '@OUTPUT@', ++ command : ['python3', gen_py_controls, '--mode', 'controls', '-o', '@OUTPUT@', + '-t', gen_py_controls_template, '@INPUT@'], + env : py_build_env) + + pycamera_sources += custom_target('py_gen_properties', + input : properties_files, + output : ['py_properties_generated.cpp'], +- command : [gen_py_controls, '--mode', 'properties', '-o', '@OUTPUT@', ++ command : ['python3', gen_py_controls, '--mode', 'properties', '-o', '@OUTPUT@', + '-t', gen_py_controls_template, '@INPUT@'], + env : py_build_env) + +-- +2.49.0 diff --git a/pkgs/libcamera-rpi/patches/libcamera-no-timeout.patch b/pkgs/libcamera-rpi/patches/libcamera-no-timeout.patch new file mode 100644 index 0000000..cdb5416 --- /dev/null +++ b/pkgs/libcamera-rpi/patches/libcamera-no-timeout.patch @@ -0,0 +1,29 @@ +From 98918c4efdcf03701908bb756f252ba11b59490b Mon Sep 17 00:00:00 2001 +From: Jordan Holt +Date: Sat, 21 Jun 2025 18:41:54 +0100 +Subject: [PATCH] libcamera no timeout + +--- + src/libcamera/ipc_pipe_unixsocket.cpp | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/src/libcamera/ipc_pipe_unixsocket.cpp b/src/libcamera/ipc_pipe_unixsocket.cpp +index 668ec73b..faf7e2a5 100644 +--- a/src/libcamera/ipc_pipe_unixsocket.cpp ++++ b/src/libcamera/ipc_pipe_unixsocket.cpp +@@ -130,11 +130,13 @@ int IPCPipeUnixSocket::call(const IPCUnixSocket::Payload &message, + /* \todo Make this less dangerous, see IPCPipe::sendSync() */ + timeout.start(2000ms); + while (!iter->second.done) { ++ #if 0 + if (!timeout.isRunning()) { + LOG(IPCPipe, Error) << "Call timeout!"; + callData_.erase(iter); + return -ETIMEDOUT; + } ++ #endif + + Thread::current()->eventDispatcher()->processEvents(); + } +-- +2.49.0 diff --git a/pkgs/rpicam-apps/package.nix b/pkgs/rpicam-apps/package.nix new file mode 100644 index 0000000..6172c98 --- /dev/null +++ b/pkgs/rpicam-apps/package.nix @@ -0,0 +1,68 @@ +{ + stdenv, + callPackage, + fetchFromGitHub, + makeWrapper, + meson, + ninja, + pkg-config, + boost, + ffmpeg-headless, + libdrm, + libepoxy, + libexif, + libjpeg, + libpng, + libtiff, + libX11, +}: +let + libcamera-rpi = callPackage (import ../libcamera-rpi/package.nix) { }; +in +stdenv.mkDerivation (finalAttrs: { + pname = "rpicam-apps"; + version = "1.7.0"; + + src = fetchFromGitHub { + owner = "raspberrypi"; + repo = "rpicam-apps"; + rev = "v${finalAttrs.version}"; + hash = "sha256-79qpAfY83YOZdM5ZPyIOkg3s7x75hvjG6Cc96UAIdb0="; + }; + + buildInputs = [ + boost + ffmpeg-headless + libcamera-rpi + libdrm + libepoxy # GLES/EGL preview window + libexif + libjpeg + libpng + libtiff + libX11 + ]; + + nativeBuildInputs = [ + makeWrapper + meson + ninja + pkg-config + ]; + + # See all options here: https://github.com/raspberrypi/rpicam-apps/blob/main/meson_options.txt + mesonFlags = [ + "-Denable_drm=disabled" + "-Denable_egl=disabled" + "-Denable_hailo=disabled" + "-Denable_qt=disabled" + "-Denable_libav=disabled" + ]; + + postInstall = '' + for f in rpicam-hello rpicam-jpeg rpicam-raw rpicam-still rpicam-vid + do + wrapProgram $out/bin/$f --set-default LIBCAMERA_IPA_PROXY_PATH ${libcamera-rpi}/libexec/libcamera + done + ''; +})