Turn any number of Android phones running DroidCam into a synchronized multi-camera studio β preview, record, and snapshot from a single Python script.
DroidGrid is a lightweight desktop controller for DroidCam. Connect several Android phones to the same Wi-Fi network, run one script, and you immediately get:
- A tiled live preview of all streams in one window
- Simultaneous recording to separate
.mp4files per camera - Instant JPEG snapshots from all cameras with one key
- Auto-reconnect when a stream drops or freezes β no babysitting required
- An inline session editor to label recordings without touching the terminal
No external server, no cloud, no paid subscriptions. Just phones, Wi-Fi, and Python.
| Feature | Detail | |
|---|---|---|
| πΊ | Live grid preview | Up to 10 phones in a tiled 3-column layout |
| π¬ | Simultaneous recording | Each camera writes its own .mp4 independently |
| π· | Snapshot (T) | One JPEG per camera, saved instantly |
| π | Self-healing streams | Frozen-frame detection via MD5 hash + auto-reconnect |
| β‘ | Non-blocking I/O | Dedicated write-queue thread per camera |
| π·οΈ | Inline session editor | Change label/person/repeat inside the preview window |
| π | Custom naming pattern | {label} {person} {repeat} {camera} {date} {time} |
| π | Live HUD | Per-cell FPS, frame counter, drop counter, REC badge |
On your PC:
pip install opencv-python numpyOn each Android phone:
- Install DroidCam (free)
- Connect to the same Wi-Fi network as your PC
- Open DroidCam β the IP address is shown on screen
git clone https://github.com/Vision-Orchestration/DroidGrid.git
cd DroidGrid
pip install -r requirements.txtOpen droidgrid.py and edit the CAMERAS list at the top:
CAMERAS = [
{"name": "Phone-1", "ip": "192.168.1.101", "port": 4747, "res": (1280, 720), "fps": 30},
{"name": "Phone-2", "ip": "192.168.1.102", "port": 4747, "res": (1280, 720), "fps": 30},
{"name": "Phone-3", "ip": "192.168.1.103", "port": 4747, "res": (1280, 720), "fps": 30},
# add more as needed
]Performance tip: For 5+ cameras, use
"fps": 20and"res": (960, 540)for smoother operation on standard hardware.
python droidgrid.pyThe preview window opens. Connected cameras appear immediately. Disconnected ones show an OFFLINE placeholder and reconnect automatically.
| Key | Action |
|---|---|
R |
Start recording on all connected cameras |
S |
Stop recording β files saved, repeat counter auto-advances |
T |
Snapshot β one JPEG per camera saved to snapshots/ |
G |
Set session label (opens inline prompt) |
P |
Set person ID |
N |
Set repeat number |
C |
Reconnect all cameras |
H |
Toggle HUD overlay |
Q |
Quit |
All prompts appear as an overlay inside the preview window β no terminal input needed.
DroidGrid/
βββ recordings/
β βββ walk_p01_r01_Phone-1.mp4
β βββ walk_p01_r01_Phone-2.mp4
β βββ walk_p01_r01_Phone-3.mp4
βββ snapshots/
βββ walk_p01_r01_Phone-1_20250421_143022.jpg
βββ walk_p01_r01_Phone-2_20250421_143022.jpg
βββ walk_p01_r01_Phone-3_20250421_143022.jpg
Files are never overwritten β if a path already exists, a numeric suffix is appended automatically.
Configured via NAMING_PATTERN in droidgrid.py:
NAMING_PATTERN = "{label}_{person}_{repeat}_{camera}"Available tokens: {label} {person} {repeat} {camera} {date} {time}
Main thread β UI rendering loop (30 fps display)
β
βββ Camera-1 βββΊ Capture thread βββΊ Queue βββΊ Writer thread βββΊ .mp4
βββ Camera-2 βββΊ Capture thread βββΊ Queue βββΊ Writer thread βββΊ .mp4
βββ Camera-3 βββΊ Capture thread βββΊ Queue βββΊ Writer thread βββΊ .mp4
βββ ...
Capture thread β reads frames, detects freezes, reconnects on failure
Writer thread β drains the queue to disk, completely independent of display
Main thread β builds the grid and handles keyboard events only, never blocks on I/O
| Failure mode | Detection | Response |
|---|---|---|
| Stream drop | cap.read() fails 10Γ |
Reconnect after 2 s |
| Frozen stream | MD5 hash identical for 60+ frames | Immediate reconnect |
All settings live at the top of droidgrid.py:
RECORD_DIR = "recordings" # video output folder
SNAPSHOT_DIR = "snapshots" # snapshot output folder
NAMING_PATTERN = "{label}_{person}_{repeat}_{camera}" # file naming tokens
CELL_W = 640 # preview cell width (px)
CELL_H = 360 # preview cell height (px)
CODEC = "mp4v" # video codec fourcc (mp4v / MJPG / XVID)
FREEZE_THRESHOLD = 60 # identical frames before reconnect
RECONNECT_DELAY = 2.0 # seconds between reconnect attemptsCamera shows OFFLINE immediately
- Confirm the IP shown in DroidCam matches the one in
CAMERAS - Confirm both devices are on the same Wi-Fi (not guest network)
- Check that Windows Firewall / antivirus is not blocking port
4747 - Try opening
http://<phone-ip>:4747/mjpegfeedin a browser β if you see a video, the URL works
Frames are dropping / stream is laggy
- Switch to a 5 GHz Wi-Fi network
- Lower
fpsto20andresto(960, 540)per camera - Reduce
CELL_W/CELL_Hto decrease display load
Video files are empty or won't open
- Try changing
CODECfrom"mp4v"to"MJPG"and use.aviextension - Check disk space
Frozen stream not detected
- Lower
FREEZE_THRESHOLD(default60) if you want faster detection - Higher values reduce false reconnects on slow networks
Q: Does it work with RTSP cameras (not just DroidCam)?
Yes. Set "ip" to the full RTSP URL and "port" to None, then adjust the url property in the Camera class to return self.ip directly.
Q: DroidCam free vs paid β which do I need?
The free version works fully. Paid removes the watermark and allows higher resolutions.
Q: How many phones can I use at once?
Tested up to 5 phones at 1280Γ720 / 30fps on mid-range hardware (RTX 3070 + Ryzen 7). For 10 phones, use 960Γ540 and 20fps.
Q: Can I use this for research / dataset collection?
Yes β that's exactly what it was built for. The naming pattern ({label}_{person}_{repeat}_{camera}) is designed for structured dataset recording.
Issues and pull requests are welcome.
- Fork the repo
- Create a branch:
git checkout -b feature/my-feature - Commit your changes with a clear message
- Open a pull request
MIT β free for personal, academic, and commercial use.
Part of the Vision-Orchestration toolkit.
Built with OpenCV. No cloud. No subscriptions. Just cameras.