This project explores data transfer using a screen-to-camera approach. The idea is simple: encode information into a
sequence of QR codes, display them as a video on a screen, and then use a camera to capture and decode the video frames
to retrieve the original data.
This method enables:
- Air-gapped data transfer between devices
- One-way communication without network connectivity
- Visual data encoding that can be recorded and processed later
Warning
This is a proof of concept. The protocol does not include encryption, so it is not secure for transmitting sensitive
data.
The project includes both encoding (generating QR code videos) and decoding (extracting data from captured
videos).
The QR Transfer (QRT) protocol defines how data is structured within each QR code frame. It provides reliable data
transfer with integrity verification and support for out-of-order frame reassembly.
All frames use | as field separator and start with a magic header QRT1 (version 1).
Header Frame (HDR) – First frame, contains transfer metadata:
| Field | Description |
|---|---|
session_id |
8-char unique identifier for this transfer |
total_chunks |
Number of data frames to expect |
total_size |
Total payload size in bytes |
encoding |
Character encoding (e.g., utf-8) |
checksum |
CRC32 of complete payload (hex) |
Data Frame (DAT) – Contains actual payload chunks:
| Field | Description |
|---|---|
session_id |
Same as header |
sequence |
Frame number (0001-9999, 1-indexed) |
total |
Total data frames |
checksum |
CRC32 of this chunk’s payload (hex) |
payload |
The actual data chunk |
Footer Frame (END) – Last frame, marks end of transfer:
| Field | Description |
|---|---|
session_id |
Same as header |
checksum |
CRC32 of complete payload for verification |
- Session tracking: Multiple concurrent transfers distinguished by session ID
- Ordering: Sequence numbers enable out-of-order reassembly
- Integrity: CRC32 checksums at chunk and message level
- Completeness: Header declares expected chunks, footer confirms end
- Versioning: Magic header includes version for future compatibility
Generates a QR code video from text input (single stream):
- Text Input: Accepts text directly or generates Lorem Ipsum placeholder text
- Protocol Framing: Wraps data in QRT protocol with header, data frames, and footer
- QR Generation: Creates a QR code image for each protocol frame
- Video Assembly: Uses ffmpeg to combine all QR code frames into a single video file
The output is an MP4 video where each frame displays a QR code containing a protocol-wrapped chunk.
Generates a video with multiple QR codes per frame for high-throughput transfer:
- Parallel Streams: Displays 1-12 QR codes simultaneously
- Grid Layout: Automatic arrangement in optimal grid
- Increased Throughput: Up to 400x+ faster than single-stream baseline
- Frame Repeat: Optional frame repetition for improved reliability
Supported grid configurations:
- 1 stream: 1×1 (baseline)
- 2 streams: 2×1
- 4 streams: 2×2 (recommended)
- 5-6 streams: 3×2
- 7-8 streams: 4×2
- 9 streams: 3×3
- 10 streams: 5×2
- 11-12 streams: 4×3 (maximum)
See BITRATE.md for detailed throughput analysis.
Decodes a captured video and extracts the original message:
- Frame Extraction: Reads video frames using OpenCV
- QR Detection: Detects and decodes QR codes from each frame using pyzbar
- Deduplication: Filters duplicate frames (same QR captured multiple times)
- Integrity Check: Verifies CRC32 checksums for each chunk
- Reassembly: Orders chunks by sequence number and reconstructs the message
- Verification: Validates final message checksum against header/footer
The decoder handles real-world conditions: camera-captured videos with motion blur, duplicates, and potentially missing
frames.
Decodes QR codes directly from a live camera feed:
- Camera Selection: Lists available cameras including iPhone (via Continuity Camera on macOS)
- Live Detection: Continuously scans for QR codes in the camera feed
- Visual Feedback: Displays bounding boxes around detected QR codes with status
- Progress Tracking: Shows real-time progress bar and statistics
- Session Management: Automatically detects new transfer sessions
Features:
- Visual overlay showing session ID, progress, and chunk count
- Color-coded QR detection (green=valid, yellow=header, magenta=footer, red=error)
- Keyboard controls for reset and save operations
- Python 3.10+
- ffmpeg installed and available in PATH
- zbar library (for QR decoding)
On macOS:
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
Generate a QR video with Lorem Ipsum:
python src/generate_qr_video.py
Generate with custom text:
python src/generate_qr_video.py -t "Your text to encode here"
Encoder options:
| Option | Description | Default |
|---|---|---|
-t, --text |
Text to encode | Lorem Ipsum |
-p, --paragraphs |
Number of Lorem Ipsum paragraphs | 3 |
-c, --chunk-size |
Characters per QR code payload | 100 |
-f, --fps |
Frames per second | 2 |
-r, --resolution |
Video resolution | 720×720 |
-o, --output |
Output filename | output.mp4 |
Generate a 4-stream (2×2 grid) video:
python src/generate_qr_video_multistream.py
Generate with 8 streams for high throughput:
python src/generate_qr_video_multistream.py -s 8 -c 500 -f 10
Generate with 12 streams for maximum throughput:
python src/generate_qr_video_multistream.py -s 12 -c 400 -f 10 -r 1920x1080
Multi-stream encoder options:
| Option | Description | Default |
|---|---|---|
-t, --text |
Text to encode | Lorem Ipsum |
-p, --paragraphs |
Number of Lorem Ipsum paragraphs | 5 |
-c, --chunk-size |
Characters per QR code payload | 400 |
-s, --streams |
Number of parallel streams (1-12) | 4 |
-f, --fps |
Frames per second | 5 |
-r, --resolution |
Video resolution | 1080×1080 |
-o, --output |
Output filename | output_multistream.mp4 |
--repeat |
Repeat each frame N times | 1 |
Decode a captured video:
python src/decode_qr_video.py captured_video.mov
Save decoded message to file:
python src/decode_qr_video.py captured_video.mov -o decoded.txt
Decoder options:
| Option | Description | Default |
|---|---|---|
-o, --output |
Save decoded message to file | stdout |
-s, --sample-rate |
Process every Nth frame (faster decoding) | 1 |
-v, --verbose |
Print detailed progress | off |
--lenient |
Continue with missing frames/checksum errors | off |
List available cameras:
python src/decode_realtime.py -l
Start real-time decoder (interactive camera selection):
python src/decode_realtime.py
Use specific camera (e.g., iPhone via Continuity Camera):
python src/decode_realtime.py -c 1
Keyboard controls during real-time decoding:
| Key | Action |
|---|---|
q |
Quit |
r |
Reset session (clear captured data) |
s |
Save decoded message to file |
Real-time decoder options:
| Option | Description | Default |
|---|---|---|
-c, --camera |
Camera index | interactive |
-l, --list |
List cameras and exit | – |
-o, --output |
Output file for decoded message | timestamped filename |