LAN screen broadcasting with adaptive UDP streaming and diff-frame encoding
Project description
mirror-space
LAN screen broadcasting with adaptive UDP streaming and diff-frame encoding.
mirror-space lets you share your screen to any machine on the same local network with minimal latency. It uses block-based differential compression — sending only the pixels that changed — to keep bandwidth low even at 30 fps, and automatically adapts quality when the network gets congested.
Features
- Diff-frame encoding — only changed screen regions are transmitted each frame
- Adaptive streaming — FPS, resolution, and JPEG quality adjust in real time based on receiver feedback
- Zero-config LAN discovery — mDNS (Zeroconf), UDP beacon, and subnet-scan discovery run in parallel so receivers find broadcasters automatically
- HWND-based window capture (Windows) — lock capture to a specific window handle so tab switches never affect the stream
- Secure session handshake — six-character access ID prevents accidental cross-session connections
- Cross-platform receiver — the receiver runs on Windows, Linux, and macOS
Requirements
| Component | Minimum version |
|---|---|
| Python | 3.10 |
| OpenCV | 4.8.0 |
| NumPy | 1.24.0 |
| mss | 9.0.1 |
| Pillow | 10.0.0 |
| zeroconf | 0.130.0 |
Windows users who want HWND-based window capture (recommended) also need:
pygetwindow>=0.0.9
pywin32>=306
Installation
From PyPI
pip install mirror-space
With Windows extras (HWND capture + window enumeration)
pip install "mirror-space[windows]"
From source (development)
git clone https://github.com/your-username/mirror-space.git
cd mirror-space
pip install -e ".[dev,windows]"
Quick Start
Step 1 — Start the broadcaster (on the machine you want to share)
mirror-broadcast
The broadcaster will:
- Prompt you to choose a capture region (full screen, specific window, or custom area)
- Print a Session Access ID (e.g.
A1B2C3) - Wait for a receiver to connect
Step 2 — Start the receiver (on the other machine)
mirror-receive
The receiver will discover broadcasters on the LAN automatically. Select the broadcaster by number and enter the Session Access ID when prompted.
Manual IP (if auto-discovery fails)
# broadcaster side — already running, noting its IP
mirror-broadcast
# receiver side — pass broadcaster IP directly
mirror-receive 192.168.1.42
Custom port
mirror-broadcast 192.168.1.255 8888
mirror-receive 8888 192.168.1.42
CLI Reference
mirror-broadcast [target_ip [port]]
| Argument | Default | Description |
|---|---|---|
target_ip |
255.255.255.255 |
Receiver IP or broadcast address |
port |
9999 |
UDP stream port (feedback on port + 1) |
mirror-receive [port [broadcaster_ip]]
| Argument | Default | Description |
|---|---|---|
port |
9999 |
UDP listen port |
broadcaster_ip |
(auto) | Skip discovery and connect directly |
Receiver keyboard shortcuts (inside the OpenCV display window):
| Key | Action |
|---|---|
1–9 |
Select discovered stream |
q / ESC |
Quit |
Library API
You can use the encoder and decoder directly in your own code:
import cv2
import numpy as np
from mirror_space import DiffFrameEncoder, DiffFrameDecoder
encoder = DiffFrameEncoder(
block_size=8,
threshold=1,
max_changed_block_ratio=0.45,
jpeg_quality=95,
)
decoder = DiffFrameDecoder()
# Encode a frame captured by any means
frame = cv2.imread("screenshot.png")
payload = encoder.encode(frame, frame_number=0)
# Decode on the receiver side
reconstructed = decoder.decode(payload)
DiffFrameEncoder parameters
| Parameter | Default | Description |
|---|---|---|
block_size |
32 |
Pixel block size for dirty-region detection |
threshold |
10 |
Per-pixel diff threshold to count a block as changed |
max_changed_block_ratio |
0.30 |
Changed-area ratio above which a key frame is sent |
max_diff_payload_ratio |
0.12 |
Payload-size ratio above which a key frame is sent |
jpeg_quality |
75 |
JPEG quality for full/key frames (30–95) |
enable_motion_detection |
True |
Enable Lucas-Kanade optical flow motion frames |
How It Works
Broadcaster Receiver
────────── ────────
ScreenCapture UDPReceiver
│ │
▼ │
DiffFrameEncoder ──UDP fragments──► Reassemble
│ │
│ KEY_FRAME (PNG, full res) ▼
│ DIFF_FRAME (PNG patches) DiffFrameDecoder
│ MOTION_FRAME (residuals) │
│ ▼
◄──STREAM_STATS / KEYFRAME_REQ── cv2.imshow
AdaptiveController
- KEY_FRAME / FULL_FRAME — complete PNG-compressed frame; sent on startup, resolution change, or high-motion scenes
- DIFF_FRAME — only the changed rectangular regions, each individually PNG-compressed
- MOTION_FRAME — Lucas-Kanade optical flow vectors + residual blocks (experimental)
- Fragmentation — frames larger than 1400 bytes are split into MTU-safe UDP fragments with a 20-byte header (
total | index | frame_number | send_time_ns)
Project Structure
mirror-space/
├── src/
│ └── mirror_space/
│ ├── __init__.py # Public API
│ ├── broadcaster.py # CLI entry point + stream logic
│ ├── receiver.py # CLI entry point + display loop
│ ├── diff_encoder.py # DiffFrameEncoder / DiffFrameDecoder
│ └── region_selector.py # Capture region / window picker
├── docs/
├── pyproject.toml
├── setup.py
├── README.md
├── LICENSE
└── .gitignore
Building and Publishing
See PUBLISHING.md for full step-by-step instructions.
License
MIT — see LICENSE for details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mirror_space-0.1.0.tar.gz.
File metadata
- Download URL: mirror_space-0.1.0.tar.gz
- Upload date:
- Size: 39.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3768fb769b9d686caccd04a6a9cf09909fc021a7af4c5623fbf836f4fd275bfc
|
|
| MD5 |
cb779ae97f12aa415fddd0f29c647b9c
|
|
| BLAKE2b-256 |
3e79951be0914945bf763fe4d9d04695e197a0b299c2fe1aef714e5de646e5d9
|
File details
Details for the file mirror_space-0.1.0-py3-none-any.whl.
File metadata
- Download URL: mirror_space-0.1.0-py3-none-any.whl
- Upload date:
- Size: 38.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1941e3a9af32feafb4c7ab804057eb1eef9ccd3e57eb4d2a7baae0761580f762
|
|
| MD5 |
9c99c0dffa6ffe64e2e77235f38d39f3
|
|
| BLAKE2b-256 |
25afc925abfc5cf52215a6b8db8972c76660768f2d309282f8282dff139aee02
|