Turn any number of Android phones running DroidCam into a synchronized multi-camera studio β preview, record, and snapshot from a single Python script.
No external server. No cloud. No subscriptions. Just phones, Wi-Fi, and Python.
Context β DroidGrid was built as the data-collection rig for FERN β, a real-time foot gesture recognition system. The naming pattern (
{label}_{person}_{repeat}_{camera}) maps directly to FERN's training pipeline.
| Feature | Detail | |
|---|---|---|
| πΊ | Live grid preview | Up to 10 phones in a tiled 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 as your PC
- Open DroidCam β the IP address appears on screen
git clone https://github.com/Vision-Orchestration/DroidGrid.git
cd DroidGrid
pip install -r requirements.txtEdit the CAMERAS list at the top of droidgrid.py:
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).
python droidgrid.pyThe preview window opens immediately. Connected cameras appear in the grid; offline ones show a placeholder and reconnect automatically.
| Key | Action |
|---|---|
R |
Start recording on all connected cameras |
S |
Stop recording β files saved, repeat counter advances |
T |
Snapshot β one JPEG per camera β snapshots/ |
G |
Set session label |
P |
Set person ID |
N |
Set repeat number |
C |
Reconnect all cameras |
H |
Toggle HUD overlay |
Q |
Quit |
All prompts appear as overlays 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_20260421_143022.jpg
βββ walk_p01_r01_Phone-2_20260421_143022.jpg
βββ walk_p01_r01_Phone-3_20260421_143022.jpg
Files are never overwritten β a numeric suffix is appended if a path exists.
Set 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 βββΊ Frame queue βββΊ Writer thread βββΊ .mp4
βββ Camera-2 βββΊ Capture thread βββΊ Frame queue βββΊ Writer thread βββΊ .mp4
βββ Camera-3 βββΊ Capture thread βββΊ Frame 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"
SNAPSHOT_DIR = "snapshots"
NAMING_PATTERN = "{label}_{person}_{repeat}_{camera}"
CELL_W = 640 # preview cell width (px)
CELL_H = 360 # preview cell height (px)
CODEC = "mp4v" # 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 in
CAMERASmatches the one shown in the DroidCam app - Confirm both devices are on the same Wi-Fi (not a guest network)
- Check that Windows Firewall / antivirus isn't blocking port
4747 - Verify the stream works: open
http://<phone-ip>:4747/mjpegfeedin a browser
Frames are dropping / stream is laggy
- Switch to a 5 GHz Wi-Fi network
- Lower
fpsto20andresto(960, 540)per camera - Reduce
CELL_W/CELL_H
Video files are empty or won't open
- Change
CODECfrom"mp4v"to"MJPG"and use a.aviextension - Check disk space
Does it work with RTSP cameras (not just DroidCam)?
Yes. Set "ip" to the full RTSP URL, "port" to None, and adjust the url property in the Camera class to return self.ip directly.
DroidCam free vs paid β which do I need?
Free works fully. Paid removes the watermark and unlocks higher resolutions.
How many phones can I use at once?
Tested up to 5 phones at 1280Γ720 / 30 fps on mid-range hardware. For 10 phones, use 960Γ540 at 20 fps.
Can I use this for research / dataset collection?
Yes β that's exactly what it was built for. The naming pattern maps directly to FERN's training pipeline.
DroidGrid/
βββ assets/
β βββ banner.svg
βββ droidgrid.py # single-file application
βββ requirements.txt
βββ CHANGELOG.md
βββ LICENSE
βββ README.md
| Project | Description |
|---|---|
| FERN | Foot gEsture Recognition Network β the ML system DroidGrid was built to feed. Real-time gesture classification via MediaPipe + CNN-BiLSTM-Attention. |
Issues and pull requests are welcome.
git checkout -b feature/my-feature
# make changes
git commit -m "feat: describe your change"
# open a pull requestSee CHANGELOG.md.
Released under the MIT License β free for personal, academic, and commercial use.
Part of the Vision-Orchestration toolkit. Built with OpenCV. No cloud. No subscriptions. Just cameras.