光学通信系统 · 二维码文件传输
发送端把任意文件切片成循环播放的二维码,接收端用摄像头扫描还原 —— 全程纯前端、零网络、单向物理隔离传输
编码库
解码库
1 · 选择文件
点击选择,或拖拽文件到此处
支持文本 / 图片 / 压缩包 / 视频等任意二进制文件
2 · 链路参数
高速 · 近距离单码上限—
每帧总载荷—
压缩—
分片总数—
理论速率—
单圈时长—
实测帧率—
预渲染—
3 · 播放二维码
待机
选择文件后在此显示二维码
帧 —/—
—
使用提示
建议流程:① 本机 A 打开“发送端”选择文件并全屏播放;② 手机/本机 B 打开“接收端”开启摄像头对准屏幕;
③ 保持画面清晰、二维码完整入框,等待进度到 100% 后下载文件。
摄像头 API 需要
https 或 localhost 环境;若解码困难,请降低二维码版本、降低帧率、提高纠错等级或增大模块像素。
想更快?不靠提帧率的三大手段(已内置):
① 多码并排——一屏放 2×2/3×3 个码,单帧吞吐直接 ×4/×9;
② Worker 并行解码——接收端多线程同时解各分区,解码不再是瓶颈;
③ 传输前 deflate 压缩——文本/日志/未压缩数据可再省 50~80%。
多码并排会让每个码变小,务必:接收端解析分辨率选 1080 px、发送端全屏播放、
摄像头正对屏幕且让整个码阵完整入框。若识别率下降,先降到 2×2 或提高模块像素。
摄像头
未开启接收进度
会话 —0%
完成度
0 / 0
已收分片
0
采样帧率
0
解码码/秒
0 B/s
有效速率
0
重复帧
0
坏帧
文件名—
大小—
缺失分片—
解码器—
校验—
日志
回环自检
不使用摄像头,直接把发送端渲染出的二维码位图喂给解码器,验证 分片 → 编码 → 渲染 → 解码 → 重组 → CRC 校验 全链路正确性。
0
总帧数
0
解码成功
0
解码失败
—
结论
自检日志
OQTP · Optical QR Transfer Protocol v1
系统结构
- 发送端(调制器):文件 →
ArrayBuffer→ 定长分片 → 加协议头与 CRC32 → 转成二维码可承载字符串 → 逐帧渲染到 Canvas 循环播放。 - 信道:屏幕光 → 空气 → 摄像头 CMOS,单向、无反馈、有丢帧,因此采用「循环轮播 + 幂等接收」来代替重传。
- 接收端(解调器):摄像头视频流 → Canvas 抽帧 → 按网格切块 → Worker 池并行 jsQR 解码 → 校验 CRC → 按序号写入分片表 → 全部到齐后拼接、校验 CRC32 →(若 META 标记压缩则 inflate)→ 生成 Blob 下载。
帧格式(共 12 字节公共头)
| 字段 | 偏移 | 长度 | 说明 |
|---|---|---|---|
| MAGIC | 0 | 2 | 0x4F 0x51('O','Q') |
| VER | 2 | 1 | 协议版本 = 1 |
| TYPE | 3 | 1 | 1 = DATA,2 = META |
| SESSION | 4 | 4 | 会话号(大端),区分不同文件 |
| CRC32 | 8 | 4 | Body 的 CRC32(大端) |
| BODY | 12 | n | 见下 |
DATA Body(8 字节子头 + 载荷)
| 字段 | 偏移 | 长度 | 说明 |
|---|---|---|---|
| SEQ | 0 | 4 | 分片序号,从 0 开始 |
| TOTAL | 4 | 4 | 分片总数(每帧都带,接收端可中途入场) |
| PAYLOAD | 8 | n | 文件原始字节切片 |
META Body
UTF-8 JSON:{ "n": 文件名, "s": 上链字节数, "t": MIME, "c": 上链数据CRC32(hex),
"k": 分片大小, "T": 分片总数, "z": 是否 deflate, "r": 解压后原始长度, "g": 多码网格 }。
首帧即发 META,之后默认每 48 帧补发一次,接收端任何时刻入场都能立刻拿到元信息与解码提示(网格数、是否压缩)。
可靠性设计
- 无重传的 ARQ 替代:发送端无限循环轮播,接收端幂等去重;缺失的分片会在下一圈自然补齐。
- 双层 CRC32:帧级 CRC 拦截摄像头误码/半帧撕裂;文件级 CRC 最终确认整文件无损。
- QR 自带 Reed-Solomon 纠错:L/M/Q/H 对应 7%/15%/25%/30% 码字可恢复,等级越高单帧容量越小但抗污损越强。
- Binary 载荷:直接使用 QR Byte 模式承载 8bit 原始字节,比 Base64 少 33% 体积;接收端读取 jsQR 的
binaryData。若设备解码不稳,可切到 Base64 模式。
提速三板斧(本工具的核心设计)
光学信道吞吐 = 单帧信息量 × 帧率 × 帧成功率。帧率被屏幕刷新率与摄像头曝光双重限制,
很难超过 20~30,所以主要从另外两个维度下手:
- ① 多码并排(空间复用):一屏同时显示 g×g 个独立二维码,单帧信息量直接 ×g²。 分区之间填中灰分隔带(既不像模块也不像静默区),接收端按同样网格切块、每块单独解码。2×2 即 4 倍,3×3 即 9 倍。
- ② Worker 并行解码(打破解码瓶颈):jsQR 是纯 JS 单线程,1080p 全幅每帧要几十毫秒,
这通常才是「实测速率上不去」的真凶。本工具起 2/4/8 个 Worker,各分块用
postMessage(buf, [buf])零拷贝转移过去并行解码;上一帧未解完则跳过当前帧,避免任务堆积。 - ③ 传输前 deflate 压缩(减少要传的量):用浏览器原生
CompressionStream('deflate-raw'),压缩后小于原始 97% 才启用(图片/压缩包压不动就原样发), META 用z/r标记,接收端自动 inflate。文本/日志/未压缩数据常见省 50~80%。
吞吐估算与预设档位
单码净载荷 = QR Byte 容量 − 20 B 协议头;每帧载荷 = 单码净载荷 × g²。
| 预设 | 版本/纠错 | 多码 | 单码载荷 | 每帧载荷 | 帧率 | 理论速率 |
|---|---|---|---|---|---|---|
| 保守 · 远距离 | v8 / Q | 1×1 | 88 B | 88 B | 5 | 0.43 KB/s |
| 均衡 | v14 / M | 2×2 | 342 B | 1368 B | 10 | 13.4 KB/s |
| 高速 · 近距离 | v20 / L | 2×2 | 838 B | 3352 B | 15 | 49.1 KB/s |
| 极速 · 贴屏 | v20 / L | 3×3 | 838 B | 7542 B | 20 | 147.3 KB/s |
表中为理论上限(无丢帧、未计压缩)。真实链路按帧成功率折算,通常能达到理论值的 30%~70%; 若数据可压缩还要再乘压缩率。页面「实测发送速率 / 有效速率 / 解码码每秒」显示的是当前真实值。
发送端也会成为瓶颈:编码 Worker 池
开了多码之后,发送端的 QR 编码反而先撑不住:4×4 每帧要编 16 个 v20 二维码,
qrcode-generator 是纯 JS,单个约 10~15 ms,一帧就 150~250 ms —— 正好对应「设 24 fps 只跑出 4 fps」。
为此做了三件事:
- ① 编码搬进 Worker 池(默认按 CPU 核数 −1,可 0~12 手动调):worker 只回传
模块矩阵(
Uint8Array,v20 仅 9.4 KB)并零拷贝转移,主线程只负责画像素。 - ② 绘制改为「小图 + 整数倍放大」:先在 1 像素/模块的小画布上用
createImageData直接写像素,再drawImage整数倍放大并关闭插值 —— 比逐模块fillRect(v20 单码 9409 次调用)快一个数量级,且边缘依然锐利。 - ③ 滑动窗口缓存 + 等待式播放:4×4 单帧位图可达数 MB,全量缓存会瞬间吃光内存并让缓存形同失效; 改为围绕播放位维护一个约 192 MB 的窗口,淘汰时按环形距离最远者先删。 播放循环只读缓存:下一帧未就绪就原地等一拍并催编码线程,绝不在主线程硬算 —— 硬算会阻塞渲染,反而更慢。
「编码器」一行显示当前线程数;若状态栏出现「等待编码」,说明编码仍跟不上, 可加线程、降多码档位或降低版本。
播放器实现要点(为什么不掉帧)
- 帧位图缓存 + 后台预渲染:每帧(含多码拼图)只算一次并缓存成离屏 Canvas,播放时仅
drawImage;预渲染由编码 Worker 池并行填充窗口,播放时零编码开销。 - requestAnimationFrame 定时:按目标间隔累加时间戳推进,避免
setInterval的抖动与堆积; 落后超过 200 ms 自动重同步。 - UI 节流:文字标签 ~8 Hz、分片位图 10 Hz 更新,DOM 操作不参与逐帧开销。
- 帧率上限受屏幕刷新率约束:60 Hz 屏最高就是 60 fps;设置超过刷新率只会丢帧不会更快, 所以显示「实测帧率」以便自查。
调参建议
- 发送端「实测帧率」远低于目标(如设 24 只跑 4):编码跟不上 —— 把「编码线程」加到 CPU 核数 −1、或把多码从 4×4 降到 3×3/2×2、或降低二维码版本。 大文件(几 MB)分片上千,务必开编码线程。
- 要更快:多码 2×2 → 3×3(吞吐 ×2.25);升二维码版本;纠错降到 L;解码 Worker 开 4~8; 可压缩数据保持「自动压缩」。
- 多码识别不稳:接收端解析分辨率提到 1080/1440、发送端全屏播放并让码阵完整入框、 提高模块像素;仍不稳就降回 2×2 或 1×1。
- 「解码码/秒」远低于「实测帧率 × g²」:解码仍是瓶颈 —— 增加 Worker 线程、降低解析分辨率, 或把接收端「解码网格」从「自动」改成与发送端完全一致(可省掉整幅回退那一次解码)。
- 糊帧:降帧率、降版本、纠错升到 Q;屏幕反光/摩尔纹则调整角度与亮度、摄像头稍微离远。
本工具在浏览器本地完成全部计算,不上传任何数据。发送端与接收端可以是同一台机器的两个窗口(用「回环自检」验证),
也可以是完全物理隔离的两台设备 —— 这正是「光学单向通信」的意义。