cimbar-bigfile

中文 English

🚀 在线试用发送端 · 拼接端 · 离线下载

cimbar-bigfile

基于 sz3/libcimbar 的大文件光学传输工具——通过多 fountain stream 并行突破单 stream 容量上限(libcimbar Mode B 单 stream wirehair cap ~40.5 MB),无网络环境下实测传输 100+ MB 文件(理论上限 ~1.2 GB / 单会话, 详见 manifest-spec)。

这是什么?

cimbar-bigfile 在 libcimbar 之上做了一层纯 HTML/JS 包装,把大文件切成 chunk(默认 10 MB,libcimbar 作者推荐 10-15 MB sweet spot,留出冗余 headroom),每块独立用 fountain code 编码(不同 encode_id),在屏幕上依次播放彩色码动画。

接收端用作者的 CameraFileCopy (CFC) 安卓应用扫码,CFC 内置的 fountain_decoder_sink 已经原生支持按 encode_id 并发分桶解码,完全无需修改。所有块收完后用浏览器打开拼接页面,拖入文件即可还原原始大文件。

[发送端 send.html]  →  屏幕动画  →  [手机 CFC]  →  保存 N 个块  →  [拼接 reassemble.html]  →  原文件

用法

准备

  1. 接收端:手机安装 CameraFileCopy (F-Droid / Google Play / GitHub Release APK)
  2. 发送端:电脑浏览器双击打开 send.standalone.html(自包含单文件版,无需联网,推荐
  3. 拼接端:任意浏览器双击打开 reassemble.html(零依赖纯 JS,无需联网)

也可以用模块化的 send.html(开发版),但需要先起本地 HTTP server(见下文 开发 段),因为浏览器在 file:// 协议下会拦截 wasm 文件加载。普通用户用 standalone 版更简单。

发送页和拼接页的界面默认使用英文。点击页面右上角的 中文 / EN 按钮可以随时切换语言;浏览器会记住选择,刷新或下次打开页面时继续使用所选语言。

发送

  1. 打开 send.standalone.html
  2. 把要传的文件拖入页面
  3. 点击 “Start”(中文界面为“开始传输”),屏幕开始播放彩色码动画
  4. 保持屏幕不动直到所有块发完

也可以在发送端先临时收集内容再发送:

需要打包时,过程全在浏览器本地完成,不上传网络;接收结果是无压缩的 cimbar-bundle-*.tar,必须再用 Windows 自带 tar、7-Zip 等工具解包。从 “添加文件夹” 选择的内容即使只有一个文件,也遵循这一规则。

💡 加速接收:手动跳转 + 锁定 + 标记已保存

大文件(多块)模式下,发送端 UI 自动切到「三栏响应式布局」:左栏 = 跳转列表,中间 = cimbar 码 canvas,右栏 = ✅ 已保存按钮 + 进度面板。

发送端 UI 主界面

默认行为是发送方按 manifest → part00 → part01 → ... → 循环回 manifest 的顺序循环播放,CFC 扫到哪块靠随机命中——100MB 文件总扫描时间约 ~55 分钟。

3 种操作消除等待

操作 效果 触发
单击块按钮 立即跳转到该块(清锁,自由循环) 鼠标单击 / Tab + Enter
双击块按钮 🔒 锁定该块(发送方专攻该块, fountain 持续补帧, CFC 下次扫到必然是它);再次双击 / 切别块 = 转移 / 解锁 鼠标双击 / Tab + Shift+Enter(键盘等效)
✅ 已保存, 跳下一块(右栏绿色主按钮) 当前块标 ✓ 已保存(持久化到 localStorage)+ 自动跳到下一个未完成块 CFC 弹”保存到哪里”对话框且保存完后点

按钮状态视觉(截图左栏):

预期收益:100MB 文件总扫描时间从 ~55 分钟降到 ~17 分钟(消除随机扫描等待)。

simpleMode(小文件 ≤ 块大小,单文件直发)下隐藏跳转列表——只有 1 个 stream,跳转无意义。

🔍 全屏扫码

传输开始后,点击发送页的 “Fullscreen code”(中文界面为“全屏显示码图”)可将 cimbar 码图放大到全屏,方便手机相机对焦和扫描。全屏模式隐藏左右操作栏,但会在码图外保留状态栏。大文件会进入 guided lock:锁定当前(或下一个未完成)分片,持续补帧,并显示当前分片、总分片数、已保存数量以及“✅ 已保存,锁定下一块”按钮;状态栏会常驻,避免手机保存期间发送端切到别的分片后误标。

接收

  1. 手机打开 CFC,对着电脑屏幕
  2. 每完成一块,CFC 会弹出”保存到哪里”对话框
  3. 每次都选同一个目录(推荐建一个 cimbar-bigfile-job1/ 目录)
  4. 全部接收完后:
    • 小文件(≤ 块大小,默认 ≤ 10 MB):直接 1 个原文件名的文件,不需要拼接,CFC 落盘的就是原文件
    • 大文件(> 块大小):N+1 个文件 manifest.json + <filename>.part00.bin<filename>.partNN.bin,需要走下面的拼接步骤

如果发送的是 cimbar-bundle-*.tar,无论它作为小文件直接接收,还是作为大文件先完成拼接,最终都需要再解包。

拼接(仅大文件需要)

  1. 把手机里的所有文件传到电脑(USB / 邮件 / 任何方式)
  2. 浏览器打开 reassemble.html
  3. 全选所有文件拖入页面
  4. 自动校验 SHA256 → 通过 → 自动下载还原后的原文件

拼接页同样默认使用英文,可通过右上角的 中文 / EN 按钮切换语言;切换不会清空已选择的文件或当前校验状态。

已知限制

故障排除

症状 可能原因 解决方法
CFC 扫不到任何码 屏幕亮度低 / 距离不对 屏幕调最高亮度,离手机 10-30 cm
CFC 扫描很慢 帧率太高摄像头跟不上 send.html 把 FPS 调到 10-12
某些块没收到 fountain 冗余不够 send.html 把 “冗余” 调到 2.0 或更高,让发送端给每块多发些帧
拼接 SHA256 校验失败 某块在传输中损坏 看哪块失败 → 重新发送(用同一 encode_id_base 重启 send.html,跳到那块)
浏览器加载 wasm 失败 file:// 协议被 CORS 拦截(开发版 send.html 才有此问题) 改用 send.standalone.html(双击即可),或用本地 HTTP server:python -m http.server 8000 访问 http://localhost:8000/send.html
文件名乱码 系统字符编码问题 manifest 强制 UTF-8,检查浏览器/手机系统编码
浏览器卡顿 文件太大 wasm 堆压力 降低单块大小(默认 10MB → 5MB)
总扫描时间太长 CFC 每次保存重置 fountain 状态,发送方循环导致命中靠运气 用新加的「跳转按钮」主动指定下一个目标 chunk(见上方”加速接收”段)

性能参考

参考环境:1080p 屏幕 + Pixel 5 + 默认参数(Mode B / 15 fps / 10 MB 块 / 2.0x 冗余)

文件大小 块数 弹窗次数 预估耗时 参考吞吐 (Mode B)
5 MB 1 块(直发,无 manifest) 1 次 ~1 分钟 ~85 KB/s
28 MB 3 块 4 次(manifest + 3 块) ~4-5 分钟 ~100 KB/s
100 MB 10 块 11 次(manifest + 10 块) ~16-20 分钟 ~106 KB/s

验证覆盖:5 MB(bundled test/test-5m.bin)+ ~28 MB(手动光学链路 end-to-end)+ 100 MB(应用层 round-trip via scripts/test-round-trip-100mb.js)。其他规模线性外推。

实际吞吐受光线、屏幕亮度、相机自动对焦稳定性影响很大。

冗余倍数 (redundancy) 怎么选

libcimbar 作者在 sz3/libcimbar#165 评论 中明确 “no penalty for redundant blocks (e.g. 3x or 4x for 10 MB chunks)”——也就是说 fountain code 的本质决定了:

redundancy 适用场景
1.2-1.5x 屏幕固定支架 + 良好光线 + 对焦稳定(aggressive 预设)
2.0x(默认) 一般室内光线 + 手持稳定(balanced 预设)
3.0-4.0x 差光线 / 反光 / 手抖明显(conservative 预设)

发送端 UI 的 redundancy 输入框上限放宽到 5.0x,覆盖极端场景。

进阶:超过 ~1.2 GB 的超大文件

单次会话受 wirehair uint16_t encode_id slot 限制,chunk_count 应保守约束在 ~120 块以内(即 ~1.2 GB at 10MB/chunk)。libcimbar 作者在 sz3/libcimbar#165 评论 提到两种突破方法,但都需要用户手动 babysit:

方法 1:变 chunk size 重启会话

“Provided you’re finished sending a chunk, you can re-use the encode_id if you slightly vary the chunk size… (e.g. 10.01 MB chunks after the first go around)”

第一轮把文件前 ~1.2 GB 用 10 MB chunk 发完;第二轮把剩余部分用稍微不同的 chunk size(如 10.01 MB 或 10.5 MB)重启发送端。wirehair 会把不同 chunk size 视为新文件,不会占用旧的 encode_id slot

方法 2:CFC 端重启 decoder 清缓存

“you can also restart the decoder to clear its cache of ‘done’ files, which will have the same effect without changing the chunk size”

把 CFC 应用整个重启一次,它的 fountain_decoder_sink 内部 “done files” 缓存被清空。之后同 encode_id 可以复用。但前一轮的部分进度也会一并丢失——只适合”前一轮已全部完成保存”后的新一轮场景。

⚠️ 两种方法都未在 cimbar-bigfile 中自动化实现,需要用户手动操作。理论上可传任意大文件,实测最大已验证至 100 MB(见上方性能表)。

开发

更新 libcimbar 依赖

详见 vendor/README.md 的 “Updating to newer libcimbar release” 章节

协议规范

本地测试发送端

# 起本地 HTTP 服务器(避免 file:// CORS 问题)
python -m http.server 8000
# 浏览器访问 http://localhost:8000/send.html

构建自包含单文件版

send.standalone.html 是给最终用户的”双击即用”版本,把 vendor wasm + glue js 都 base64 inline 进 HTML,脱离 HTTP server。每次升级 vendor 后必须重新构建:

PYTHONIOENCODING=utf-8 PYTHONUTF8=1 python scripts/build-standalone.py
# 输出 send.standalone.html (约 2.5 MB)

构建脚本只读 send.html + vendor/cimbar-wasm-v0.6.4/cimbar_js.*.js + cimbar_js.*.wasm,输出独立文件到仓库根目录。原理:替换 <script src="vendor/..."> 为 inline <script> 把 base64 解码成 Module.wasmBinary,emscripten glue 检测到就跳过 fetch。

架构与协议

License & Acknowledgements

cimbar-bigfile 是 libcimbar 之上的轻量包装器。本仓库由 MIT、MPL-2.0、BSD-3-Clause 三个许可证共同管理

部分 许可证 版权 说明
send.html / reassemble.html / docs/* 等本项目原创代码 MIT © 2026 peipei 详见 LICENSE
vendor/cimbar_js.html / vendor/cimbar-wasm-v0.6.4/* MPL-2.0 © sz3 (libcimbar) 详见 vendor/LICENSE-libcimbar
上述 wasm 内嵌的 wirehair fountain code 库 BSD-3-Clause © 2018 Christopher A. Taylor 详见 vendor/LICENSE-wirehair
用户独立安装的 CFC Android 接收端 (未 vendor) MIT © sz3 github.com/sz3/cfc

MPL-2.0 source obligation:本项目分发了 libcimbar 的 wasm 二进制(Executable Form),按 MPL-2.0 §3.2,对应的 source code 可在 https://github.com/sz3/libcimbar/tree/v0.6.4 免费获取。

致谢