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:
QRT1|HDR|<session_id>|<total_chunks>|<total_size>|<encoding>|<checksum>
| 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:
QRT1|DAT|<session_id>|<sequence>|<total>|<checksum>|<payload>
| 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:
QRT1|END|<session_id>|<checksum>
| 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: 1x1 (baseline)
- 2 streams: 2x1
- 4 streams: 2x2 (recommended)
- 5-6 streams: 3x2
- 7-8 streams: 4x2
- 9 streams: 3x3
- 10 streams: 5x2
- 11-12 streams: 4x3 (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:
brew install ffmpeg zbarpython3 -m venv .venv
source .venv/bin/activatepip install -r requirements.txtGenerate a QR video with Lorem Ipsum:
python src/generate_qr_video.pyGenerate 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 | 720x720 |
-o, --output |
Output filename | output.mp4 |
Generate a 4-stream (2x2 grid) video:
python src/generate_qr_video_multistream.pyGenerate with 8 streams for high throughput:
python src/generate_qr_video_multistream.py -s 8 -c 500 -f 10Generate with 12 streams for maximum throughput:
python src/generate_qr_video_multistream.py -s 12 -c 400 -f 10 -r 1920x1080Multi-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 | 1080x1080 |
-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.movSave decoded message to file:
python src/decode_qr_video.py captured_video.mov -o decoded.txtDecoder 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 -lStart real-time decoder (interactive camera selection):
python src/decode_realtime.pyUse specific camera (e.g., iPhone via Continuity Camera):
python src/decode_realtime.py -c 1Keyboard 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 |