voxl

temboMIT活跃维护

用 Rust 实现的超快 Web 远程桌面串流工具,通过 WebCodecs 把 Linux/macOS 桌面低延迟推到浏览器,方便给 AI Agent 或沙箱提供可视化操作界面。

extremely fast web remote desktop streaming

v0.3.4发布于 2026-11-07 00:23 UTC1,603star +308 / 7 天88fork同步于 2026-11-10 17:49 UTC

怎么上手

由 README 提炼,执行前请确认
cargo install voxl-server
DISPLAY=:1 voxl
硬件要求按 README 说明

README 的基准测试在无 GPU 的 16 vCPU AMD EPYC 9334 虚拟机上完成,说明可以纯 CPU 运行;README 未说明具体显存要求,也未列出支持的 GPU 厂商与型号。

纯 CPU 可运行
下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

由 README 自动翻译提炼

用 Rust 写的低延迟浏览器远程桌面串流工具,把 X11、Mac 或 Windows 屏幕编码成 H.264 经 WebSocket 推给浏览器

用 Rust 实现的超快 Web 远程桌面串流工具,通过 WebCodecs 把 Linux/macOS 桌面低延迟推到浏览器,方便给 AI Agent 或沙箱提供可视化操作界面。

核心能力

  • 用 x264 把桌面编码成 H.264,经 WebSocket 推流,浏览器用 WebCodecs 解码
  • 自适应码率,可在 2 Mbps 到 30 Mbps 之间调节
  • 支持输入、剪贴板、音频、麦克风、文件与文件夹传输
  • 支持动态分辨率,可让桌面跟随查看器窗口缩放
  • 兼容 selkies 的命令行参数,便于迁移已有启动配置

容易踩的坑

编译前需系统已装 x264、libyuv、libopus、libpulse 和 esbuild,否则 cargo install 会失败;默认监听 0.0.0.0:6901 且开启密码登录,需用启动时打印的带 token 链接访问;想要零拷贝采集需给 Xvfb 加 -shmem,平滑滚动还需使用打过补丁的 Xvfb。

内容由模型根据 README 生成,可能不完整或不准确,以官方文档为准。看原文 README

最近发版

3 个版本v0.3.4
  1. v0.3.4voxl 0.3.4最新正式版2026-11-07 · 2026-11-07 00:23 UTC

    What's Changed

    New Contributors

    Full Changelog: https://github.com/tembo/voxl/compare/v0.3.3...v0.3.4

  2. v0.3.3voxl 0.3.32026-11-04 · 2026-11-04 15:43 UTC
    查看更新说明

    What's Changed

    New Contributors

    Full Changelog: https://github.com/tembo/voxl/compare/v0.3.2...v0.3.3

  3. v0.3.2voxl 0.3.22026-11-02 · 2026-11-02 20:59 UTC
    查看更新说明

    What's Changed

    Full Changelog: https://github.com/tembo/voxl/compare/v0.3.1...v0.3.2

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译

    voxl

    crates.io

    Low-latency desktop streaming for the browser, written in Rust. voxl streams an X11 display, a Mac's screen or a Windows desktop as H.264 over a WebSocket to a small web client that decodes with WebCodecs, with input, clipboard, audio, microphone, file and folder transfer and dynamic resolution.

    Quick start

    cargo install voxl-server   # needs x264, libyuv, Opus, PulseAudio and esbuild (see Usage)
    DISPLAY=:1 voxl             # streams display :1 and prints a link that signs you in
    

    Open the link it prints. On a Mac or a Windows PC, voxl streams that machine's own screen instead; see macOS and Windows.

    Benchmarks

    https://github.com/user-attachments/assets/acdf3f31-64fc-4acf-b5c5-ff173d7100a8

    In every test below voxl averages 60 FPS with a median latency of 12–99 ms; noVNC averages under 8 FPS.

    A 16 vCPU AMD EPYC 9334 VM (no GPU), 2560x1600 Xvfb desktop, same workloads, each stream decoded by a native client that reads a pixel marker drawn into the content. Cells are FPS (worst second), then latency p50/p99 from the content being drawn to the frame being decoded. voxl's column is its best run of each test. To reproduce, see bench/.

    TestvoxlselkiesKasmVNCNekoNeko, tunedSunshineSunshine, tunednoVNCDaytona
    Chromium scroll, LAN60.1 (59.7), 55/60 ms56.8 (54.0), 85/106 ms51.7 (39.5), 99/135 ms25.0 (23.7), 156/217 ms53.9 (49.9), 128/155 ms28.3 (23.5), 88/115 ms48.5 (44.0), 94/117 ms6.7 (6.3), 198/301 ms5.2 (2.5), 306/713 ms
    Scroll, 10 Mbps / 40 ms RTT60.0 (59.4), 82/94 ms55.6 (47.7), 152/261 ms8.3 (7.4), 229/249 ms24.6 (14.9), 221/737 ms46.3 (40.3), 606/831 ms27.9 (25.3), 160/179 ms51.3 (47.5), 147/162 ms2.6 (2.4), 426/678 ms1.9 (1.6), 617/798 ms
    Scroll, 5 Mbps / 60 ms RTT60.1 (59.7), 99/109 ms27.9 (19.6), 2.7/3.7 s4.9 (3.6), 306/415 ms21.7 (3.8), 1.2/3.6 sstalls29.6 (21.9), 178/207 ms44.5 (35.2), 164/191 ms1.6 (1.4), 637/937 ms1.1 (0.9), 1.1/1.2 s
    1080p animation, LAN60.1 (59.9), 12/16 ms58.7 (55.9), 31/48 ms56.7 (51.6), 51/82 ms24.8 (23.6), 122/202 ms55.6 (50.0), 54/82 ms27.1 (22.7), 69/91 ms45.6 (40.3), 42/57 ms7.7 (7.2), 149/210 ms10.5 (8.8), 138/209 ms
    Animation, 10 Mbps / 40 ms RTT60.1 (59.5), 46/71 ms39.3 (31.8), 2.3/3.1 s52.9 (49.2), 76/115 ms25.1 (9.1), 295/962 ms33.3 (25.4), 4.0/7.1 s29.7 (26.0), 108/125 ms51.1 (44.6), 86/107 ms5.8 (5.2), 160/226 ms7.4 (7.0), 154/213 ms
    Animation, 5 Mbps / 60 ms RTT60.0 (58.8), 66/92 ms18.4 (15.3), 2.2/4.3 s34.1 (26.4), 104/156 ms15.2 (8.6), 5.0/9.4 sstalls34.7 (25.4), 123/143 ms45.5 (40.9), 114/131 ms5.1 (4.7), 174/240 ms5.8 (4.4), 189/252 ms
    Full-screen animation, LAN59.8 (54.0), 18/25 ms54.8 (49.8), 54/79 ms34.2 (32.5), 83/102 ms22.7 (18.0), 2.9/3.3 s54.6 (49.7), 54/79 ms24.9 (21.7), 73/98 ms47.4 (43.4), 46/65 ms5.9 (5.2), 184/279 ms9.1 (7.2), 154/247 ms
    Server CPU, X server + streamer (cores)idle1080p animationscrollfull screen
    voxl0.001.391.331.68
    selkies1.29not measured1.541.80
    KasmVNC0.001.972.071.82 (at 34 FPS)
    Neko1.632.50 (at 25 FPS)2.20 (at 25 FPS)2.46 (at 23 FPS)
    Neko, tuned1.902.282.302.52
    Sunshine2.102.34 (at 27 FPS)2.24 (at 28 FPS)2.35 (at 25 FPS)
    Sunshine, tuned2.843.07 (at 46 FPS)3.14 (at 49 FPS)3.22 (at 47 FPS)
    noVNC0.010.55 (at 8 FPS)0.58 (at 7 FPS)0.64 (at 6 FPS)
    Daytona (1 CPU limit)0.020.82 (at 10 FPS)0.53 (at 5 FPS)0.81 (at 9 FPS)
    • selkies: selkies with picom running.
    • KasmVNC: 1.4.0 with a 60 FPS cap, JPEG/WebP quality 8–9 and video mode, no compositor.
    • Neko: m1k1o/neko 3.2 with its defaults (VP8, 25 FPS). Neko, tuned captures at 60 FPS and encodes with x264 ultrafast/ zerolatency at up to 30 Mbps, like voxl. Neither adapts its bitrate (Neko's estimator is off by default), so on slow links frames queue up; "stalls" means no frame arrived in 20 s.
    • Sunshine: Sunshine 2025.628 streaming to Moonlight (moonlight-embedded 2.7, patched to hand frames to the benchmark client). Sunshine is built around GPU capture and encoding; without a GPU it falls back to copying the X11 screen and x264, so these numbers show it on a GPU-less VM like the other tests, not on the gaming PC it's made for. Sunshine runs its defaults (superfast on 2 threads) at Moonlight's default 40 Mbps; Sunshine, tuned uses ultrafast on 4 threads at 30 Mbps, like voxl (8 or 16 threads did no better). Neither reaches 60 FPS on this VM, and it keeps working while the screen is still (its idle CPU). Moonlight doesn't adapt its bitrate, so on slow links it's set to 80% of the link. Its CPU was measured in a later session, where voxl used 1.74, 1.65 and 1.99 cores on the same workloads.
    • noVNC: x11vnc and noVNC as E2B Desktop and Daytona ship them (-forever -wait 50 -shared, Tight encoding). x11vnc polls the screen, which caps it at 6–8 FPS.
    • Daytona: a default sandbox (1 CPU, x11vnc and noVNC at 2560x1600) streamed through Daytona's signed preview proxy, about 12 ms away, which its latency includes. The workloads run inside the sandbox, sharing its CPU, and slow links are added on top of that internet path.
    • Links: a bottleneck with a deep queue: a TCP proxy for the WebSocket servers, and tc netem for Neko's WebRTC media and Sunshine's video (UDP). None of them drop packets, where UDP with error correction would have the edge.

    Usage

    Build (needs Rust, pkg-config, clang, x264, libyuv, libopus, libpulse and esbuild; nix-shell provides them all):

    nix-shell
    cargo build --release
    

    Or install it from crates.io with the same libraries on your system (the crate is voxl-server, the binary is voxl):

    cargo install voxl-server
    

    Run it against an X display, then open the link it prints (?stats adds an FPS overlay):

    DISPLAY=:1 ./target/release/voxl --port=6901
    # prints: open http://localhost:6901/?token=4f1c… to sign in
    

    Start Xvfb with -shmem for zero-copy capture, and use the patched Xvfb (nix-build -E 'with import <nixpkgs> {}; callPackage ./xvfb.nix {}') for smooth scrolling. voxl accepts selkies' command-line flags; other selkies flags are ignored. voxl --help lists every flag with its default, and voxl --version prints the version.

    FlagDefault
    --addr, --port0.0.0.0, 6901listen address
    --titleVoxl Computer Streamthe page's title: the browser tab, and the app's name if a viewer installs it
    --faviconvoxl's iconthe tab's icon: an .ico, .png, .gif, .webp or .svg file
    --enable-basic-authtrueask for a password (see Login); false serves without one
    --basic-auth-passwordVOXL_PASSWORD, else made upthe password
    --basic-auth-useranythe user name a browser's sign-in has to give
    --framerate60max frames per second (60-60 style ranges accepted)
    --presetultrafastx264 preset
    --encoder-threadsmin(4, ncpu/4)x264 sliced threads
    --crf24base quality
    --max-kbps / --video-bitrate30000adaptive bitrate ceiling
    --min-kbps2000adaptive bitrate floor
    --paintover-crf, --paintover-burst, --paintover-delay-ms16, 3, 150sharpening when the screen goes still
    --enable-clipboardtruetrue, in, out or false
    --audio-enabled, --audio-device-name, --audio-bitratetrue, output.monitor, 128000desktop audio
    --enable-resize, --resize-scalefalse, 1.5desktop follows the viewer's window; desktop pixels per CSS pixel
    --microphone-enabled, --microphone-fifofalse, /tmp/voxl-mic.fifobrowser microphone into the FIFO a PulseAudio pipe source reads
    --round-corners0 (off)corner radius in pixels for top-level windows
    --round-windowsnonetitle:radius,... rounds windows with that title, docks included
    --adaptive-threadsfalseencode on one thread, fanning out only under sustained heavy motion
    --stripes1encode the screen as N independent horizontal streams (experimental)
    --scroll-px-per-notch180desktop pixels one wheel notch scrolls (calibrates smooth scrolling)
    --file-transfersupload,downloadupload, download, both, or none
    --read-onlyfalseviewers only watch: input, clipboard writes, the microphone, uploads and resizing (--enable-resize) are refused
    --allowed-hostsnonehost names to answer for besides IP addresses and localhost (.example.com covers subdomains, * any)
    --allowed-originsnoneother sites whose pages may connect, such as a page that embeds voxl's client (* any site)
    --files-root$HOMEfolder the Files panel can see; nothing outside it is reachable
    --upload-dirDownloadsfolder (under the root) the panel opens and uploads into; created on first upload
    --max-upload-mb0 (no limit)largest accepted upload
    --max-record-seconds300longest recording (/record)
    --convert-threads, --coalesce-us4, 0tuning

    Login

    voxl asks for a password before it shows the desktop or takes input, files or API calls. Give it one with --basic-auth-password or VOXL_PASSWORD; otherwise it makes one up when it starts and prints a link that signs you in. There are three ways in:

    • The link: http://host:6901/?token=<password>. The page passes the password on to its own connections, so the link also works as an iframe's src.
    • The browser's sign-in prompt, which voxl's page brings up: any user name (unless --basic-auth-user names one) and the password. Password managers offer to save it.
    • For programs: Authorization: Bearer <password>, or curl -u :<password>.

    The page's scripts and /healthz load without it. If voxl sits behind a proxy that signs people in itself, --enable-basic-auth=false turns voxl's login off.

    Hosts and origins

    voxl only answers requests for an IP address or localhost, and only from its own page. That keeps out other web pages, which browsers let connect to any address, including pages whose domain is pointed at voxl's address (DNS rebinding). To reach voxl under a host name, such as through a proxy or as my-pc.local, name it with --allowed-hosts. To let a page on another site connect, name that site with --allowed-origins. Programs that aren't browsers (curl, scripts, agents) send no Origin and connect as usual. A refused request gets a 403 that names the flag to add.

    For agents and scripts

    Screenshots and recordings (start voxl with VOXL_PASSWORD set, and send it along):

    # the screen as a PNG
    curl -u ":$VOXL_PASSWORD" -o shot.png localhost:6901/screenshot
    # an MP4 of the next 10 s
    curl -u ":$VOXL_PASSWORD" -o clip.mp4 "localhost:6901/record?seconds=10"
    # or start...
    id=$(curl -u ":$VOXL_PASSWORD" -s -X POST localhost:6901/record/start | jq .id)
    # ...and stop when done
    curl -u ":$VOXL_PASSWORD" -o clip.mp4 -X POST "localhost:6901/record/stop?id=$id"
    

    A recording is the H.264 voxl already sends to viewers, saved as MP4 without encoding it again, so it costs no extra CPU. It doesn't include the cursor (viewers draw it), and a resize ends it.

    /wait returns when the screen settles or changes, so an agent doesn't have to sleep after every action and hope it was long enough:

    # once nothing changed for 300 ms
    curl -u ":$VOXL_PASSWORD" localhost:6901/wait
    # still for 1 s, giving up after 30 s
    curl -u ":$VOXL_PASSWORD" "localhost:6901/wait?idle=1000&timeout=30000"
    # the first change in that rectangle
    curl -u ":$VOXL_PASSWORD" "localhost:6901/wait?change&region=0,0,800,600"
    # {"changed":true,"timed_out":false,"seq":812,"waited_ms":340}
    

    idle (ms, default 300) is how long the screen has to stay still, counted from the request at the earliest so an action just taken has time to show; change returns at the first change instead. region=x,y,w,h only watches that rectangle (to the 16-pixel block) and timeout (ms, default 10000) gives up. changed says whether anything changed while waiting. A change can land before the request does, so to wait for what an action caused, pass since the seq of an earlier /wait or the X-Voxl-Seq header of a screenshot: /wait?change&since=812 returns at once if anything changed after it. A blinking text caret redraws about twice a second, so keep idle under 500 ms or leave the caret out of region. voxl already knows which parts of the screen change (XDamage, ScreenCaptureKit and DXGI report them), so waiting needs no screenshots or polling.

    macOS

    voxl also streams a Mac's own screen, with every feature of the Linux version (ScreenCaptureKit capture, CGEvent input, NSPasteboard clipboard, system audio).

    brew install x264 opus pkgconf esbuild
    cargo build --release
    ./target/release/voxl --port=6901
    

    Open the link it prints. On a Mac voxl listens on 127.0.0.1 unless --addr says otherwise, since the stream is your whole screen. macOS asks voxl (or the terminal that starts it) for Screen Recording and Accessibility permissions. For the microphone, install a loopback device (brew install blackhole-2ch) and pick it as the input in the apps that should hear you.

    macOS flagDefault
    --displaymain displaydisplay to stream: an index (0 is the main display) or a display ID; logged at startup
    --capture-scalenative, within the budgetstreamed pixels per point
    --max-capture-pixels4096000 (2560x1600)pixel budget for the automatic scale: x264's cost grows with area
    --microphone-devicefirst loopback device foundoutput device (name or prefix) to play the browser microphone into
    --resize-scalethe capture scaleso a viewer's window maps to the same number of points on the Mac
    --show-cursorfalse (true with --read-only)include the host cursor in the video stream (for viewers/observers)

    The other flags work as on Linux; --audio-device-name, --microphone-fifo and the rounded-corner flags don't apply.

    Windows

    voxl also streams a Windows desktop (Windows 10 or later), with every feature of the Linux version (DXGI Desktop Duplication capture, SendInput, clipboard text and images, WASAPI system audio). Build it with Rust, vcpkg, LLVM (for bindgen) and esbuild:

    vcpkg install x264 libyuv opus --triplet x64-windows-static-md
    $env:VCPKGRS_TRIPLET = 'x64-windows-static-md'
    npm install -g esbuild
    cargo build --release
    .\target\release\voxl.exe --port=6901
    

    Open the link it prints. As on a Mac, voxl listens on 127.0.0.1 unless --addr says otherwise. It streams the desktop of the session it runs in, so start it in the user's session (not as a service); Windows hides the lock screen and UAC prompts from it unless it runs as SYSTEM. For the microphone, install a virtual audio cable (VB-CABLE): voxl plays into "CABLE Input" and apps record from "CABLE Output".

    Windows flagDefault
    --displayprimary displaydisplay to stream, by index (0 is the primary); logged at startup
    --audio-device-namedefault output deviceoutput device (name or prefix) whose sound to stream
    --microphone-devicefirst virtual cable foundoutput device (name or prefix) to play the browser microphone into
    --resize-scalethe display's scalingso a viewer's window maps to the same size on the desktop

    The other flags work as on Linux; --microphone-fifo and the rounded-corner flags don't apply. Dynamic resolution picks the largest display mode that fits the viewer's window, and Windows restores the original mode when voxl exits.

    FAQ

    Why a WebSocket and not WebRTC? One WebSocket goes wherever a web page does: through reverse proxies, tunnels and sandbox preview URLs, with no TURN servers or UDP ports to open. The browser decodes each frame the moment it arrives with WebCodecs, without WebRTC's jitter buffer, and voxl runs its own rate control, which keeps latency low on a clean link. The trade-off is TCP: on a link that drops packets (bad Wi-Fi, a weak mobile signal) a lost packet holds up the frames behind it, where WebRTC can skip ahead.

    How is it different from Moonlight and Sunshine? They're for playing games on your own GPU machine through the Moonlight app, and for that they're the better fit. voxl was built for things like software factories that use headless VMs and sandboxes (CI, computer-use agents), usually on Xvfb with no GPU. It streams them to a plain browser tab over one WebSocket and has APIs for agents.

    How is it different from RustDesk? RustDesk is an open-source alternative to TeamViewer and AnyDesk, for reaching your own computers or helping someone with theirs: you install its app on both ends, desktop or phone, and the two find each other through an ID server, then connect directly or through a relay. For that it's the better fit, and it also shares Wayland desktops (experimentally) and Android phones, which voxl doesn't. voxl is for desktops on servers, VMs and sandboxes: it's one process that serves its own page, so there's nothing to install on the viewing side and no ID or relay server to run. The stream goes wherever a URL does, can be embedded in another page and comes with the APIs agents use. RustDesk has a web client too, but it still goes through the ID and relay servers, and the self-hosted one comes with RustDesk Server Pro.

    Why doesn't the stream load at http://<ip>:6901? Browsers only allow WebCodecs on HTTPS pages and on localhost. To watch from another machine, put voxl behind a TLS proxy (Caddy, Cloudflare Tunnel, tailscale serve) and name its host with --allowed-hosts, or forward the port over SSH and open localhost.

    Is it safe to expose? voxl asks for a password by default but serves plain HTTP, so put it behind TLS away from localhost. See SECURITY.md.

    Which browsers and devices work? Any browser with WebCodecs, on an HTTPS page or localhost:

    BrowserWorks
    Chrome, Edge and other Chromium browsers 94+, desktop and AndroidYes
    Firefox 130+, desktopYes
    Safari 16.4+ and any browser on iPhone or iPad (sound needs Safari 26)Yes
    Firefox for AndroidNo

    Phones and tablets get touch input and an on-screen keyboard.

    Wayland? Not yet; on Linux voxl streams X11 displays, Xvfb included. The virtual desktops voxl is built for run on Xvfb (E2B's and Daytona's do too), and X11 lets voxl capture them without a permission prompt, with damage events that keep a still screen at no CPU and power /wait. Wayland support is being worked on.

    Does it use the GPU? Not yet. voxl encodes H.264 on the CPU with x264: 1.3–1.7 cores for a busy 2560x1600 desktop, X server included (see the CPU table above). Hardware encoding on macOS is in progress.

    What's the license? voxl's code is MIT. It links x264, which is GPL-2.0, so a voxl binary, and anything that bundles one, is distributed under the GPL.