☰
Decimen 传输排障指南:从“画面静止“到“版本不匹配“的完整排查链路
2026/10/9 13:53:05 网站建设 项目流程
  • 前端
  • 通信

【免费下载链接】decimen-optical-transfer

项目地址:https://gitcode.com/gh_mirrors/de/decimen-optical-transfer
点击查看免费下载

导读

Decimen 是一个通过屏幕 QR 码流实现设备间单向光学传输的开源项目(发送端把文件编码为连续 QR 帧,接收端用摄像头解码)。由于这条链路没有回传信道,一旦某个环节失配,故障往往表现为"摄像头在跑、画面在动、却一个字节都解不出来"。本文以 docs/user/troubleshooting.md 为主体,系统梳理三类高频故障——无信号(Nothing happening)、版本不匹配(Update 提示)、摄像头异常——并延伸到慢速传输的调优手段。读完你可以按文档给出的顺序逐条修复,同时了解这些建议在源码层面的实现依据,做到"知其然,也知其所以然"。


一、"Nothing happening?":无信号提示是怎么工作的

1.1 提示行为与触发规则

如果摄像头运行了一段时间却一帧都解不出来,接收页预览上方会弹出一条小型 toast:"Nothing happening?"(没有任何画面解码出来)。toast 上有两个按钮:

  • Help:打开详细排查建议列表;
  • Dismiss:暂时关闭提示,但它之后还会回来——因为点一下按钮并不会让帧开始到达。只有当真正解析出第一帧时,提示才会永久消失。

这套"何时提示、何时重提"的时序策略不是散落在页面里的setTimeout,而是封装在 shared/no-signal.ts 的NoSignalHintTimer类中,纯计时策略、不含任何 DOM 逻辑。其规则非常清晰:

  1. 倒计时从摄像头启动那一刻开始,使用较短的首次延迟;
  2. 用户Dismiss 后按更长的延迟重新倒计时——建议已经被看过一遍,提示不必再频繁打扰;
  3. 摄像头重启视为一次全新尝试,回到短延迟;
  4. 第一帧解析成功即永久终止,无论提示当时是否在屏幕上——这是唯一真正表明链路已经打通的信号。

在 receive/main.ts 中,两个延迟被定义为NO_SIGNAL_FIRST_MS = 8_000(首次 8 秒)和NO_SIGNAL_DISMISSED_MS = 15_000(Dismiss 后 15 秒)。这些规则都有对应的单元测试逐一验证,见 tests/no-signal.test.ts:例如"提示只在倒计时结束后的第一次 tick 触发一次""Dismiss 后恢复更长的延迟""已解码过一帧则任何后续重启都不再提示"等边界情况都被覆盖。

1.2 修复顺序:问题几乎总在发送端

排障文档反复强调一个反直觉的事实:"无信号"的修复动作在发送端。按文档给出的顺序依次尝试:

顺序操作说明
1打开发送端Transfer settings,把bytes / frame 降到 1465默认 2953 字节是为近距离手机对手机调优的,在臂长距离的普通显示器上恰好是最容易失败的配置
2仍然无解?把发送端tx fps 降到 24降低帧率给摄像头更多曝光与对焦时间
3让接收画面充满整个取景框,并把手机靠稳/架住手持微颤导致的自动对焦来回搜索(autofocus hunting)是常见的元凶
4把发送屏幕的亮度调到最高增加 QR 码的对比度与信噪比

注意顺序不能跳:先降密度(bytes/frame),再降速率(tx fps)。如果一上来就降 fps 而帧仍然过密,问题依旧。

1.3 源码层面的证据:建议值与下拉框的"构造性一致"

这些建议值不是文档里随手写的数字,而是与发送端 UI 共享的常量。见 shared/send-settings.ts:

export const NO_SIGNAL_HINT_FRAME_BYTES = 1465; export const NO_SIGNAL_HINT_TX_FPS = 24;

发送端的下拉选项TX_FPS_OPTIONS(10 / 15 / 20 /24/ 30 / 55 /60)与FRAME_BYTES_OPTIONS(500 / 1000 /1465/ 1850 / 2331 /2953)都以这两个常量构造,而接收端无信号提示的文案也由同一批常量生成(见 receive/main.ts)。这意味着提示给出的建议值永远存在于发送端的下拉框里——不会出现"建议设一个 UI 里根本不存在的数值"这类脱节。文件头注释明确写道:无信号提示命中的兜底值来自这里,因此建议不可能指向发送端不提供的设置。

为什么默认的 2953 字节 / 60 fps 会在普通显示器上失效?shared/send-settings.ts 的注释给出了关键约束:一帧至少需要在屏幕上停留约 2 个刷新周期,否则摄像头捕获会正好截到帧切换的瞬间;在 60 Hz 屏幕上每帧恰好只获得一个刷新周期,早期实测捕获率只有 0.2~0.4。所以默认值是为 120 Hz 高刷发送端与近距离场景设计的"最优演示配置",而非普适配置——这正是排障文档把 1465 和 24 作为第一、第二修复手段的原因。选项中的 55 也刻意低于 60 Hz 天花板,让帧边界在扫描过程中漂移而非骑在刷新时钟上,避免连续两帧在同一位置撕裂。

1.4 顺带提示:帧大小与块数上限的关系

把 bytes/frame 调低要留意一个边界:帧头用 u16 编号源块,上限 65535 块(MAX_SOURCE_BLOCKS = 0xffff,见 shared/frame-capacity.ts)。因此"文件大小上限"并不总是 64 MB——在 500 字节/帧下,真正的上限大约只有 30 MB。发送端会在开始传输前用fitsInOneStream()检查,若超限会明确报错并给出"把 bytes / frame 提高到某个值或以上"的建议(该建议值也一定在下拉框选项内,见 send/main.ts)。也就是说,调低帧密度既能修复"无信号",也可能触发大文件的容量报错,两者都是同一个机制在工作。


二、"Update the sending device" / "Update this app":版本不匹配

2.1 发生了什么

当接收端识别出"这是 Decimen 流,但我读不了"时,会弹出上述两条更新提示之一。其本质是:两台设备处于不同的线缆格式(wire format)上。Decimen 0.5.0 改变了帧格式,与 0.4.x 不兼容——两端必须都在 0.5.0 或更新版本上才能互通。

2.2 提示的方向语义

消息会指明落后的是哪一侧,按你看到的那条对号入座:

  • "Update the sending device"(更新发送设备)——落后的是屏幕那一端;
  • "Update this app"(更新本应用)——落后的是你手里这台手机。

2.3 关键的"单向哑火":0.4.x 接收端对 0.5.0 发送端毫无反应

这两条提示是 0.5.0新增的能力,因此存在一个不对称场景:接收端停留在 0.4.x 或更早、发送端是 0.5.0 时,接收端什么也显示不出来——只有第一节的 "Nothing happening?" toast。原因是旧接收端根本不知道帧里还有个版本号字段。这正是 docs/technical/versioning.md 所讲的版本化设计动机:v1→v2 曾经在格式变更上花了一次 magic 升级却没换来版本字段,导致"发送端太旧"与"光线不好"看起来完全一样;从 v3(随 0.5.0 发布)开始,接收端必须能说出"到底是以下哪一种情况":

判定含义接收端行为
ok可解码解码
foreign不是 Decimen 帧静默(摄像头会看到视野内每一个二维码)
older-senderDecimen,但格式更旧"Update the sending device."
newer-senderDecimen,格式更新"Update this app to receive it."
unsupported-flagsDecimen,带本端无法实现的特性"Update this app to receive it."
malformed是我们的帧,但自相矛盾静默(与坏读取无法区分)

这套判定逻辑集中在 shared/protocol.ts 的classifyFrame()中,屏幕文案则由frameVerdictMessage()统一生成(shared/protocol.ts)——它紧挨着格式定义存放,正是为了"判断结果与提示文案永远不会漂移",且任何客户端(网页、未来的 iOS/Android)对同一失败都说同样的话。双 magic 字节(0xD1 0xC3)的作用是:在说出任何版本信息之前先回答"这到底是不是我们的帧"——仅凭单个0xD1把关时,约 1/256 的随机二进制 QR 载荷会误入版本分支,被错误地要求更新一台从没运行过 Decimen 的设备;两个字节把关后误报率从 0.402% 降到 0.006%。

如果你确认发送端已是最新、接收端却依然"失明",先更新接收端。从 0.5.0 起两端都能指名格式不匹配,所以未来再发生格式变更时,无论哪一端更旧,接收屏幕上都会说清楚。另一种"什么都不显示"的可能则是接收端在看根本不属于 Decimen 的二维码——这类情况故意保持静默(详见第一节,接收端会解码视野里的每一个 QR 码,包括橱窗和商品包装上的)。

2.4 修复方法

  • 使用 decimen.app 托管站点:在两台设备上重新加载页面。如果某台设备被安装到了主屏幕(PWA)后仍提示不兼容,彻底关闭应用再重新打开,以强制刷新 service worker 缓存。
  • 使用独立单文件:从旧版本保存下来的decimen-sender.html与decimen-receiver.html彼此可以永久互通,但不能与更新版本的对方配合。需要从同一个发布版本重新下载两者。详细说明见 docs/user/install-and-offline.md——其中解释了托管站点、两个独立文件、演示模式三种形态,以及为什么独立接收文件从file://打开时拿不到摄像头(见下文第三节)。

三、摄像头问题:选错镜头、权限拒绝、非安全上下文

3.1 选错摄像头(前置/长焦)

有些手机会把错误的镜头当作"后置摄像头"交给浏览器,导致画面里要么是前置自拍,要么是一支只有站到房间另一头才清晰的长焦。修复方式:Receive settings → camera,在列表里选择正确的镜头。注意两点:

  • 列表在摄像头启动后才显示真实镜头名称(浏览器在授予权限前会隐藏镜头名);
  • 切换立即生效,传输中途也可以切换。

3.2 权限被拒绝(Permission denied)

浏览器弹出权限询问时要点得仔细——如果不小心点了 Block,需要到浏览器设置里为该站点允许摄像头,然后回到页面点Start camera重新启动(无需刷新页面)。

3.3 "camera needs a secure context"

该报错意味着页面正通过明文 http提供服务。浏览器会在非安全来源上整体移除摄像头 API。解决方式:

  • 通过https提供服务——项目自带的开发服务器就是 https(自签名证书);
  • 或使用托管站点 decimen.app。

这正是 docs/user/quick-start.md 中npm run dev启动的是 https 开发服务器的原因(localhost是豁免的,但你手机访问的局域网 IP 不是)。

3.4 独立接收文件无法获得摄像头

在 iOS 或 Android 上,从file://直接打开decimen-receiver.html不会获得摄像头——本地文件拿到的是不透明来源(opaque origin),移动端浏览器不给本地文件提供相机权限。解决方式见 docs/user/install-and-offline.md:把该文件放到任意 http(s) 服务器上供手机访问,或者改用托管站点的离线模式。发送端没有这个问题,decimen-sender.html在所有平台都能从file://直接运行。


四、慢速传输:调优的两个杠杆

如果链路已经建立(帧能解出来)但传输龟速,问题就从"能不能解"转为"解多快"。排障文档指向 docs/user/sending.md 的调优表——bytes/frame 和 tx fps 是唯二真正起作用的旋钮:

设置默认值说明
tx fps60为 120 Hz 发送端调优;在 60 Hz 屏幕上如果接收端停滞,降到 24–30
bytes / frame2953(QR v40)密度天花板——近距离手机对手机很好;对显示器或远距离要回退到 1465(v27)
error correctionLfountain 层已经处理擦除(丢帧),L 在这些帧尺寸下是正确的取舍
display size900 px受屏幕上限约束;全屏模式忽略此值

默认值偏向最佳演示场景。若传输爬行:按顺序bytes/frame → 1465,tx fps → 24——与第一节"无信号"的修复顺序完全一致。需要理解的是,帧内纠错(QR 的 ECC)与 fountain 层解决的是两类不同问题:前者应对帧内局部损坏(corruption),后者应对整帧丢失(erasure)。在"整帧解码或丢弃 + fountain 冗余"的策略下,L 级纠错配合约 K·1.15 的冗余帧(docs/technical/protocol.md)才是这尺寸帧的最优组合。


五、快速定位速查表

综合以上四节,把症状与修复手段收敛成一张速查表,便于现场快速定位:

症状优先怀疑修复
摄像头在跑,一帧不解(toast 弹出)发送端配置过密/过快按序执行:bytes/frame → 1465 → tx fps → 24 → 稳住手机 → 拉满亮度
显示"Update the sending device"接收端比发送端新更新发送端设备到 0.5.0+
显示"Update this app"发送端比接收端新更新接收端到 0.5.0+
接收端毫无反应且发送端已最新接收端是 0.4.x 或更旧 / 或在看非 Decimen 码先更新接收端;确认视野中确实是 Decimen 流
画面是前置镜头或长焦模糊浏览器选错镜头Receive settings → camera 切换(可传输中途切换)
提示 permission denied误点 Block浏览器允许站点摄像头,点 Start camera,无需刷新
提示需要安全上下文明文 http走 https(dev server 已自带自签名证书)或托管站点
独立接收文件拿不到摄像头file://不透明来源放到 http(s) 服务器,或使用托管站点离线模式(详见 docs/user/install-and-offline.md)
能解码但很慢密度/速率失衡bytes/frame → 1465,tx fps → 24,按此顺序

结语:先读提示,再动设置

Decimen 的排障哲学可以浓缩为两句:静默失败比大声失败更糟糕(所以 0.5.0 之后任何读不了的 Decimen 流都会指名方向),修复动作几乎总在发送端(所以 "Nothing happening?" 的 toast 指向的是另一台设备上的两个下拉框)。从源码看,这两条哲学都不是靠文档约定,而是被写进了常量共享(shared/send-settings.ts)、帧判定逻辑(shared/protocol.ts)与计时策略(shared/no-signal.ts)中。遇到问题时按本文速查表从前往后逐条尝试,多数情况下第 1~2 步就能让画面重新流动起来。

  • 前端
  • 通信

【免费下载链接】decimen-optical-transfer

项目地址:https://gitcode.com/gh_mirrors/de/decimen-optical-transfer
点击查看免费下载

相关推荐

上一篇:HCCL experimental/ 实验空间贡献指南:目录规范、运行期开关与维护策略全解析
下一篇:new-api 计费表达式系统(billingexpr)全解析:一行表达式定义完整计费逻辑

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询