光学通信系统 · 二维码文件传输

发送端把任意文件切片成循环播放的二维码,接收端用摄像头扫描还原 —— 全程纯前端、零网络、单向物理隔离传输

编码库 解码库

1 · 选择文件

点击选择,或拖拽文件到此处
支持文本 / 图片 / 压缩包 / 视频等任意二进制文件

2 · 链路参数

高速 · 近距离
单码上限
每帧总载荷
压缩
分片总数
理论速率
单圈时长
实测帧率
预渲染

3 · 播放二维码

待机
选择文件后在此显示二维码
帧 —/—

使用提示

建议流程:① 本机 A 打开“发送端”选择文件并全屏播放;② 手机/本机 B 打开“接收端”开启摄像头对准屏幕; ③ 保持画面清晰、二维码完整入框,等待进度到 100% 后下载文件。
摄像头 API 需要 httpslocalhost 环境;若解码困难,请降低二维码版本、降低帧率、提高纠错等级或增大模块像素。
想更快?不靠提帧率的三大手段(已内置): ① 多码并排——一屏放 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 字节公共头)

字段偏移长度说明
MAGIC020x4F 0x51('O','Q')
VER21协议版本 = 1
TYPE311 = DATA,2 = META
SESSION44会话号(大端),区分不同文件
CRC3284Body 的 CRC32(大端)
BODY12n见下

DATA Body(8 字节子头 + 载荷)

字段偏移长度说明
SEQ04分片序号,从 0 开始
TOTAL44分片总数(每帧都带,接收端可中途入场)
PAYLOAD8n文件原始字节切片

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 / Q1×188 B88 B50.43 KB/s
均衡v14 / M2×2342 B1368 B1013.4 KB/s
高速 · 近距离v20 / L2×2838 B3352 B1549.1 KB/s
极速 · 贴屏v20 / L3×3838 B7542 B20147.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;屏幕反光/摩尔纹则调整角度与亮度、摄像头稍微离远。
本工具在浏览器本地完成全部计算,不上传任何数据。发送端与接收端可以是同一台机器的两个窗口(用「回环自检」验证), 也可以是完全物理隔离的两台设备 —— 这正是「光学单向通信」的意义。
WeChat QR Code

Scan to connect