How fast each channel is, where the time goes, and how the app keeps the UI smooth while it decodes camera frames or audio in real time. All the numbers come from the formulas in the code or from the test suite’s measurements. The derivations are in Calculations.
Back to the documentation index.
The Nominal column is calculated from each profile’s timing. Estimated goodput multiplies it by the decode rates measured in the camera and room simulators and by the fountain overhead. The ranges are recommendations. None of these columns comes from a study across many phones, so real devices can be faster or slower; see Known Issues §6.
| Channel | Nominal | Estimated goodput | Recommended range |
|---|---|---|---|
| Light, Auto (v8–v12, 12 fps) | 1.9–4.0 KB/s | ≈1.3–2.5 KB/s | 15–40 cm |
| Light, Safe (v8, 8 fps) | 1.28 KB/s | ≈0.9 KB/s | Weak cameras |
| Light, Fast (v17, 12 fps) | 7.2 KB/s | ≈2.5 KB/s if the camera keeps up | Good cameras only |
| Sound, Rugged / Safe / Standard / Fast | 10.8 / 18.1 / 27.0 / 35.8 B/s | ≈80% of nominal after fountain overhead | 0.3–2 m |
| Sound, Silent Robust / Silent (inaudible) | 3.4 / 5.0 B/s | ≈80% of nominal | 0.1–0.5 m, phone-dependent |
| Vibration (experimental) | ≈0.52 B/s raw | Not measured: real transfers usually fail (Known Issues §2.8) | Touching |
Light is about 35–70× faster than the fastest Sound profile. That’s why photos and videos go by Light.
Light (Auto, typical camera):
| Payload | ETA |
|---|---|
| Text message | ≈1 s |
| 2 KB photo sample (3.4 KB envelope after re-encode) | ≈3 s |
| 5 KB photo sample (8.6 KB) | ≈5 s |
| 20 KB photo sample (33 KB) | ≈16–17 s |
| 80 KB video | ≈38 s |
| 140 KB video | ≈65 s |
| 120 KiB photo (compression cap) | ≈57 s |
Sound:
| Payload | Rugged | Safe | Standard | Fast | Silent | Silent Robust |
|---|---|---|---|---|---|---|
| “sos” (10 B) | 11.9 s | 10.6 s | 9.5 s | 7.2 s | 19.1 s | 28.6 s |
| 100-character text (107 B) | 20.8 s | 15.9 s | 11.8 s | 8.9 s | 43 s | 64 s |
| 500 B | 65 s | 42 s | 28 s | 21 s | 2.3 min | 3.5 min |
| 2 KB photo sample (3.4 KB) | ≈6.7 min | ≈4.0 min | ≈2.8 min | ≈2.1 min | too slow | too slow |
These are the expected times (⌈1.25·K⌉ + 2 frames). When every frame lands, a receiver finishes after exactly K frames: “sos” is one frame, 2.4 s on Standard and 4.8 s on Silent.
Vibration (calculated; the channel is experimental and real transfers usually fail): “hi” ≈73 s, “hello” ≈79 s, a full 48-byte packet ≈148 s.
per displayed frame (83 ms at 12 fps):
sender: LT symbol (XOR of up to ~21 blocks) + APCF header + CRC-32 < 1 ms
QR matrix with fixed mask 0 ≈ 4 ms (15–40 ms with mask search)
paint (merged dark runs, no anti-aliasing) ≈ 1 frame
receiver: camera capture (30 fps) 33 ms apart
Y-plane copy of a 706×706 crop ≈ 0.5 MB memmove
zxing2 decode in the isolate tens of ms
LT ingest (incremental Gauss-Jordan) < 1 ms (K = 422)
The bottleneck is the camera’s decode rate, not the CPU. A frame is lost when:
The fountain code turns all of these into “a bit more time” instead of failure. Auto density keeps the QR version where the decode rate stays high: v8 about 70%, v12 about 55%, v17 about 31%, v20 about 13% in the camera simulator.
per frame (Standard, 2.368 s):
2 048-sample sync marker 46 ms
25 data symbols × 4 096 samples 2.32 s
per burst: 120 ms of lead silence, then 2–4 frames back-to-back
1.25·K + 2 frames in expectation, so at least 25% extra airtime for frame loss.| Work | Thread | Why it stays smooth |
|---|---|---|
| QR rendering | UI thread | Fixed mask (≈4 ms), integer module sizes, merged rectangles, repaint only when the bitmap changes |
| Camera frame extraction | Camera callback (UI isolate) | Y-plane row copies only; skipped entirely when the decoder is busy |
| QR decoding | One long-lived background isolate | TransferableTypedData (no copy), at most one frame in flight, 2 s reply timeout |
| LT decoding | UI isolate | Incremental: each symbol costs at most about K × K/32 word XORs (≈41 000 for K = 422), well under a millisecond |
| Audio capture and conversion | Plugin thread → Dart | PCM16 converted straight into a Float32List (no per-sample boxing) |
| Tone detection and RS decoding | Dart isolate (UI) | Goertzel on the needed bins only; RS is O(n·P) per frame |
| Live frequency readout, receiver | Dart isolate (UI) | Each mic chunk is only copied into a 1024-sample ring. The FFT (1024 points, precomputed twiddles and window, about 5 000 butterflies) runs only when the 80 ms UI throttle fires, so at most 12.5 times a second |
| Live frequency readout, sender | UI isolate | No audio analysis: the tone schedule is written while the burst is rendered (a few hundred segments), and each 50 ms tick is a binary search. The timer runs only while a burst is playing |
| Frequent UI updates | UI isolate | The readout has its own acousticSpectrumState notifier, so only its card rebuilds, not the screen |
| Timers | UI isolate | 80 ms receive poll, 500 ms metrics tick, 1 800 ms idle timer |
Frames are dropped, not queued, when the decoder is busy. Queueing would add latency and memory without adding information, because the fountain only needs some frames, not all frames.
| Item | Size |
|---|---|
| One camera luminance crop | ≈0.5 MB (706 × 706) |
| LT decoder, K = 422, 330-byte blocks | ≈163 KB (coefficient rows + payload rows) |
| LT decoder, K = 1 133 | ≈0.5 MB |
| Up to 3 concurrent Light sessions | 3 × the above, worst case |
| Sound burst WAV (4 Standard frames) | ≈0.8 MB PCM16 |
| Largest envelope accepted by Light | 8 MiB (soft cap); practical < 200 KB |
| Component | Size |
|---|---|
| Universal release APK | ≈58 MB |
| Bundled samples (16 photos + 9 videos) | 1 238 839 B ≈ 1.2 MB |
| Branding assets (logo + mark) | 103 925 B ≈ 100 KB |
flutter build apk --split-per-abi produces much smaller per-architecture APKs; most of the universal size is the Flutter engine and native plugin libraries for every CPU architecture.
From the test suite (Testing):
| Measurement | Result |
|---|---|
| 2 KB file, Auto, typical camera, 30% frame loss | Completes after 22 frames shown (13 decoded), ≈1.8 s |
| 2 KB file, hard camera at 1.5× zoom | Completes after 23 frames shown (14 decoded), ≈1.9 s |
| Old 800-byte density, hard camera | 0 frames decoded in 20 s |
| 40 KB photo / 30 KB video through real QR + zxing2 + LT at 30% loss | Byte-exact |
| Fountain overhead (measured mean) | 0–2.2 symbols |
| Full test suite | 101 tests (100 run, 1 skipped), ≈39 s |
Full optical density sweep (OPTICAL_SWEEP=1) |
≈3 min 16 s |
| Situation | Setting | Gain |
|---|---|---|
| Good receiver camera, steady hands | Light Standard or Fast | Up to 2× on large files |
| Weak camera | Light Safe + 2× zoom at 25 cm | Completes instead of stalling |
| Quiet room, phones close | Sound Fast | 1.3× over Standard |
| Big file | Always Light | 35–70× over Sound Fast |
| Many receivers | Light Auto (sparse codes) | Every camera decodes independently; no extra time |
More in Light Channel and Sound Channel.