This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

xerxes: add android-webcam tool for using phone as webcam

Uses scrcpy to stream phone camera via v4l2loopback.
Optional --ascii flag enables GPU-accelerated ASCII art filter
via ffmpeg libplacebo shader.

+161
+16
2configs/android-webcam.nix
··· 1 + { config, pkgs, self, ... }: 2 + { 3 + # v4l2loopback with two devices: raw camera and ASCII output 4 + boot = { 5 + kernelModules = [ "v4l2loopback" ]; 6 + extraModulePackages = [ config.boot.kernelPackages.v4l2loopback ]; 7 + extraModprobeConfig = '' 8 + options v4l2loopback devices=2 video_nr=0,1 exclusive_caps=1,1 card_label="Phone Camera","ASCII Webcam" 9 + ''; 10 + }; 11 + 12 + environment.systemPackages = [ 13 + pkgs.android-tools 14 + self.packages.${pkgs.system}.android-webcam 15 + ]; 16 + }
+1
machines/xerxes/config.nix
··· 11 11 ../../2configs/rtl-sdr.nix 12 12 ../../2configs/browsers.nix 13 13 ../../2configs/container-bridge.nix 14 + ../../2configs/android-webcam.nix 14 15 ]; 15 16 system.stateVersion = "25.05"; 16 17
+83
tools/android-webcam/android-webcam.sh
··· 1 + #!/usr/bin/env bash 2 + 3 + SHADER_PATH="@shaderPath@" 4 + ASCII_MODE=false 5 + 6 + while [[ $# -gt 0 ]]; do 7 + case $1 in 8 + --ascii) 9 + ASCII_MODE=true 10 + shift 11 + ;; 12 + *) 13 + echo "Unknown option: $1" >&2 14 + echo "Usage: android-webcam [--ascii]" >&2 15 + exit 1 16 + ;; 17 + esac 18 + done 19 + 20 + # Check if v4l2loopback module is loaded 21 + if ! lsmod | grep -q v4l2loopback; then 22 + echo "Error: v4l2loopback kernel module is not loaded" >&2 23 + echo "Make sure you have rebooted after enabling the android-webcam module" >&2 24 + exit 1 25 + fi 26 + 27 + if $ASCII_MODE; then 28 + # ASCII mode needs both devices 29 + if [[ ! -e /dev/video0 ]] || [[ ! -e /dev/video1 ]]; then 30 + echo "Error: /dev/video0 and /dev/video1 must exist for ASCII mode" >&2 31 + echo "v4l2loopback should be configured with devices=2" >&2 32 + exit 1 33 + fi 34 + else 35 + if [[ ! -e /dev/video0 ]]; then 36 + echo "Error: /dev/video0 must exist" >&2 37 + exit 1 38 + fi 39 + fi 40 + 41 + cleanup() { 42 + echo "Stopping..." 43 + kill $SCRCPY_PID 2>/dev/null || true 44 + [[ -n "${FFMPEG_PID:-}" ]] && kill $FFMPEG_PID 2>/dev/null || true 45 + } 46 + trap cleanup EXIT 47 + 48 + # Start scrcpy to stream phone camera to /dev/video0 49 + echo "Starting phone camera stream..." 50 + scrcpy \ 51 + --video-source=camera \ 52 + --camera-facing=back \ 53 + --no-audio \ 54 + --v4l2-sink=/dev/video0 \ 55 + --video-encoder=c2.exynos.h264.encoder \ 56 + --camera-size=1920x1080 & 57 + SCRCPY_PID=$! 58 + 59 + if $ASCII_MODE; then 60 + sleep 2 61 + 62 + # Start ffmpeg with ASCII shader to /dev/video1 63 + echo "Starting ASCII filter pipeline..." 64 + ffmpeg \ 65 + -f v4l2 -i /dev/video0 \ 66 + -vf "libplacebo=custom_shader_path=${SHADER_PATH}" \ 67 + -f v4l2 -pix_fmt yuv420p /dev/video1 & 68 + FFMPEG_PID=$! 69 + 70 + echo "" 71 + echo "Android ASCII webcam running!" 72 + echo " Raw camera: /dev/video0 (Phone Camera)" 73 + echo " ASCII output: /dev/video1 (ASCII Webcam)" 74 + else 75 + echo "" 76 + echo "Android webcam running!" 77 + echo " Camera: /dev/video0 (Phone Camera)" 78 + fi 79 + 80 + echo "" 81 + echo "Press Ctrl+C to stop" 82 + 83 + wait
+39
tools/android-webcam/ascii.hook
··· 1 + //!HOOK MAIN 2 + //!BIND HOOKED 3 + //!DESC ASCII art filter 4 + 5 + const int SCALE = 6; 6 + const bool MONOCHROME = false; 7 + const vec3 BASE_COLOR = vec3(0.0, 1.0, 0.0); 8 + 9 + float character(int n, vec2 p) { 10 + p = floor(p * vec2(4.0, 4.0) + 2.5); 11 + if (clamp(p.x, 0.0, 4.0) == p.x && clamp(p.y, 0.0, 4.0) == p.y) { 12 + int a = int(round(p.x) + 5.0 * round(p.y)); 13 + if (((n >> a) & 1) == 1) return 1.0; 14 + } 15 + return 0.0; 16 + } 17 + 18 + vec4 hook() { 19 + vec2 iResolution = HOOKED_size; 20 + vec2 pix = HOOKED_pos * iResolution; 21 + vec2 cell = floor(pix / vec2(float(SCALE) * 8.0)) * vec2(float(SCALE) * 8.0); 22 + vec4 c = HOOKED_texOff((cell - pix) / iResolution * HOOKED_size); 23 + float gray = 0.3 * c.r + 0.59 * c.g + 0.11 * c.b; 24 + 25 + int n; 26 + if (gray <= 0.2) n = 4096; 27 + else if (gray <= 0.3) n = 65600; 28 + else if (gray <= 0.4) n = 332772; 29 + else if (gray <= 0.5) n = 15255086; 30 + else if (gray <= 0.6) n = 23385164; 31 + else if (gray <= 0.7) n = 15252014; 32 + else if (gray <= 0.8) n = 13199452; 33 + else n = 11512810; 34 + 35 + vec2 p = mod(pix / vec2(float(SCALE) * 4.0), vec2(2.0)) - vec2(1.0); 36 + if (MONOCHROME) c.rgb = BASE_COLOR; 37 + c.rgb = c.rgb * character(n, p); 38 + return c; 39 + }
+22
tools/android-webcam/flake-module.nix
··· 1 + { ... }: 2 + { 3 + perSystem = 4 + { pkgs, ... }: 5 + let 6 + ffmpeg-placebo = pkgs.ffmpeg.override { withPlacebo = true; }; 7 + in 8 + { 9 + packages.android-webcam = pkgs.writeShellApplication { 10 + name = "android-webcam"; 11 + runtimeInputs = [ 12 + pkgs.scrcpy 13 + ffmpeg-placebo 14 + pkgs.kmod 15 + ]; 16 + text = builtins.replaceStrings 17 + [ "@shaderPath@" ] 18 + [ "${./ascii.hook}" ] 19 + (builtins.readFile ./android-webcam.sh); 20 + }; 21 + }; 22 + }