分析摄像机 Web 接口时,拿到一份请求列表只是开始。真正影响客户端能否使用的,往往是请求之间的关系:哪个请求只负责临时预览,哪个才会保存;JSON 登录成功后,文件上传是否还有另一套认证状态;电机动作返回成功后,位置是否已经稳定。

这次对大华 Web5.0 固件的分析,最终整理出 100 种已观测 RPC、52 个配置名称和两个音频文件 HTTP 接口,其中 44 个配置成功读取。下面记录几个决定接入实现的实验,具体请求结构见 大华 RPC2 接入实践 从登录会话到图像编码与镜头控制。

抓包与主要测试发生在 2026 年 10 月 2 日至 3 日,结论对应当时的测试设备和固件。本文使用占位符替代地址与认证信息,不公开原始会话、设备标识和内部路径。

从单个界面操作定位请求

进入设置页面后,浏览器会同时读取配置、查询能力、创建对象和发送保活。部分页面还会调用 restoreTemporaryConfig 撤销临时配置,因此不能把页面初始化产生的请求全部视为只读操作。

比较有效的分析顺序是:

保存完整配置基线
  → 只改变一个控件
  → 定位新增请求和 multicall 子调用
  → 比较目标表与关联表
  → 回读确认变化
  → 使用客户端独立复现
  → 恢复基线并再次核对

system.multicall 需要拆开分析。其 params 是子请求数组,外层 result=true 并不代表每一项都成功,参数映射和错误判断都应落实到子请求 ID。

界面截图能够说明操作位置,真正证明配置生效的是保存响应与回读差异。脱离页面调用接口,也不自动意味着认证流程已经独立完成:如果复用了浏览器的 session 或 Cookie,还需要单独记录这项前提。

第一次登录失败实际上是挑战

浏览器向 /RPC2_Login 发送的第一次 global.login,password 是空字符串:

{
  "method": "global.login",
  "params": {
    "userName": "<USER>",
    "password": "",
    "clientType": "Web5.0"
  },
  "id": 1
}

本机响应是 result=false、错误码 268632079,同时给出 realm、random 和临时 session。它们用于构造第二次认证请求,不能直接套用普通业务请求的失败处理。

本次 Default 模式采用两次摘要计算:

H1 = MD5_UPPER(userName + ":" + realm + ":" + plainPassword)
H2 = MD5_UPPER(userName + ":" + random + ":" + H1)

字符串使用 UTF-8,十六进制摘要转大写。第二次请求发送 H2 和当前挑战数据,成功后保存正式 session。

到这里,只验证了 RPC 登录流程。随后出现的文件上传问题表明,HTTP 文件入口还依赖 Cookie。

一个亮度滑块揭示了三种操作

亮度适合用于建立最小实验:初始值明确,改动小,也容易恢复。一次 50→51 的修改得到以下顺序:

顺序

调用

观察结果

1

getConfig VideoColor

Brightness=50

2

setTemporaryConfig VideoColor

临时值改为 51

3

setConfig VideoColor

提交保存值 51

4

getConfig VideoColor

回读为 51

5

重新设置并保存原值

最终回读为 50

这区分了临时预览、保存和查询。撤销临时配置不能替代恢复已经保存的值。

界面只改一项 请求仍发送完整表

浏览器虽然只改变 Brightness,实际 setConfig 仍携带完整 VideoColor 数组,其他 profile 和未知字段都保留。因此客户端中的“补丁”只能是本地操作,提交设备前要合并回完整表。

本次配置中有 9 个具名 profile,应根据 Name 定位。VideoColor 等表使用 table[channel][profileIndex],而 VideoImageControl、AISSA、LDCorrection 是通道级 table[channel],不能把它们都解释成 Day 或 Night 的参数。

能力字段存在不等于当前组合可写

测试设备在 3840×2160 条件下拒绝 Rotate90=1。VideoInIRExposure 可以读取,但能力返回 Support=false。这说明“查询成功”“保存某个字段成功”和“实际功能效果”需要分开验证。

完整原表能够保存,也不能证明其中每个字段都允许修改。

AI 开关修改的不止一个字段

场景自适应新增了关联字段

AISSA.Enable 看起来只是一个布尔开关,但启用后,VideoInBacklight 和 VideoInDefog 出现了新增的 Snapshot 元数据:

位置

变化

AISSA[0].Enable

false→true

VideoInBacklight[0][0].Snapshot

从不存在变为存在

VideoInDefog[0][0].Snapshot

从不存在变为存在

如果恢复程序只把 Enable 改回 false,配置可能仍与基线不同。备份必须区分“原来没有这个键”和“原值就是 null”;对于本次新增的键,恢复动作可能是删除,而非赋值为 null。

AI 编码需要分两次写

启用 AICoding 会联动修改 Encode。本次观察到主码流的 encodeType 变为 2,码率控制使用 ABR,AiGOP 与 FPS 关联;25 FPS、2 秒策略对应 AiGOP=50。

客户端先构造完整候选 Encode,查询动态能力,再写 AICoding、回读检查联动,最后写 Encode 并核对两张表。若反过来先写 Encode,随后开关触发的联动就可能覆盖刚刚保存的值。

另一个容易误判的细节是 MainFormat:本机配置有 4 条记录,能力却只返回 3 条。这些记录不代表 4 条独立主码流,缺失的能力项也不能由客户端复制补齐后当作已经验证。

报警音测试中,浏览器能够上传 WAV 文件,Python 也已经能读取配置、列出文件和调用播放接口,但上传会收到 RemoteDisconnected。

测试文件仅 3116 字节,采用 8000 Hz、16 bit、单声道 PCM WAV,时长为 0.192 秒。文件足够小,下载后还能通过字节数与 SHA256 检查是否与原文件一致。

先做单变量对照

最初根据导出记录重建 multipart,并逐项尝试请求头、连接和 boundary:

对照组

有效 Cookie

结果

复制浏览器非敏感请求头

无

连接被设备关闭

复用同一条已认证 TCP 连接

无

连接被设备关闭

使用捕获到的 boundary

无

连接被设备关闭

原请求增加有效 Cookie

有

上传成功,下载哈希一致

仅保留必要请求头和 Cookie

有

上传成功,下载哈希一致

urllib 携带 Cookie 和 RPC session

有

上传成功,下载哈希一致

这些对照把关键差异指向了 WebClientHttpSessionID。它们并不意味着所有上传断连都由 Cookie 引起,但足以说明本次设备的上传流程不能只依赖 JSON RPC 会话。

脱敏导出没有记录 不代表线上没有发送

用于分析的脱敏 HAR 没有保留 Cookie 与 Set-Cookie,后来检查浏览器实际请求头才确认它们的存在。问题出在把“导出记录没有这个头”当成了“浏览器没有发送这个头”。

修复后,客户端分别保存 RPC session 与 HTTP Cookie。即使某次两个值恰好相同,也不在代码中硬编码两者的对应关系。

最初成功的上传复用了已授权浏览器的认证状态,这只能证明上传需要哪些条件。为了确认客户端能独立运行,又使用新会话执行普通密码登录,明确不导入浏览器 session 或 Cookie:

获取挑战
  → 使用密码摘要完成认证
  → 从 Set-Cookie 接收 WebClientHttpSessionID
  → 上传新文件
  → 下载并核对字节数与 SHA256
  → 播放与停止
  → 恢复测试状态并核对配置
  → 注销

这一轮中,挑战响应没有 Set-Cookie,第二阶段认证成功响应下发 Cookie。客户端通过内存 CookieJar 自动接收和发送认证数据,上传无需手工复制浏览器状态。Cookie 自动处理机制可以参阅 Python 官方文档。

复测确认上传内容一致,播放返回 Status=OK,停止成功,恢复后 30 张配置表与基线一致,最后完成注销。

成功回调后仍要校验文件

上传响应不是 JSON RPC,而是 HTML 中的一段回调:

<script>if(parent.audioUploadStatue) parent.audioUploadStatue(1);</script>

客户端按固件的原始拼写解析 audioUploadStatue,不执行响应中的脚本。HTTP 200 和成功回调之后,还要确认文件存在、下载字节数一致、哈希一致。播放控制则另行检查返回状态,不能用上传成功代替播放成功。

变倍动作会改变聚焦位置

镜头状态中的 Focus 与 Zoom 都是 0~1 的归一化位置。步数可以结合 FocusMotorSteps 和 ZoomMotorSteps 换算,这些范围应从设备读取。

本次初始状态对应 Focus=480 步、Zoom=0 步。将 Zoom 增加一步之后,Zoom 到了 1,但 Focus 稳定在 476:

操作

稳定回读

聚焦加一步

Focus 480→481,Zoom=0

聚焦减一步

Focus 481→480,Zoom=0

变倍加一步

Zoom 0→1,Focus 480→476

先恢复变倍,再恢复聚焦

Zoom=0,Focus=480

这说明同时发送 focus 和 zoom,并不保证两轴最后都保持请求值。Zoom 变化触发的自动聚焦可能覆盖 Focus,因此指定两个目标时,要先变倍、等待稳定,再设置聚焦。

参考客户端在连续两次 Status=Normal 且两轴位置稳定后,再校验目标值。动作请求成功、处于 Normal、达到目标位置是不同条件,不能只检查其中一项。

相似按钮也可能使用不同报文

动作

方法

params 形态

顶层 object

混合自动聚焦

devVideoInput.autoFocus

空字符串

有

人脸目标聚焦

devVideoInput.setFocus

FocusType、Channel

无

区域聚焦

devVideoInput.focusRegion

mode、rect

有

面板重置

devVideoInput.adjustFocus

focus=-1、zoom=-1

有

页面下拉框切换聚焦目标时可能只改变前端状态,直到点击自动聚焦才发送控制。不能用一次下拉框操作推断设备已经改变目标类型。

区域聚焦使用 8192 尺度的原生矩形坐标,不能直接代入预览窗口像素。此次测试使用 [3000,2000,5000,4500],没有穷举边缘位置和旋转后的换算。

9 个镜头案例通过,最终恢复到初始位置和混合目标,31 张配置表与基线一致。这轮镜头测试复用了已授权浏览器会话,验证的是控制响应与稳定位置,不应与前面独立密码上传测试混为一轮。

保留每一轮测试的范围

第一轮功能验证共 44 项,其中 43 项通过、1 项直接上传失败。上传问题经过 Cookie 对照定位后,再由独立密码登录复测确认修复,不能把最初的结果直接改写成 44/44。

阶段

已确认的结果

图像设置

21 个指定功能案例通过

视频编码

9 个指定参数组合案例通过

主辅码流开关

5 个配置开关案例通过

音频参数

6 个指定参数案例通过

首轮报警音功能

3 项中,直接上传失败,其他 2 项通过

Cookie 对照与密码登录复测

确认上传认证条件,并完成独立登录后的上传播放流程

镜头控制

另一个轮次的 9 个案例通过

这些验证也有明确层级:VideoEnable 回读改变,不等于测量了媒体连接停流;startPlay 返回 OK,不等于验证了扬声器音质;聚焦位置稳定,不等于完成了光学清晰度评估。

这次分析最有价值的几项结果,都来自请求之外的检查:用回读区分临时预览与保存,用关联表差异发现 AI 开关副作用,用 Cookie 对照补齐独立登录流程,用稳定位置确认镜头动作。把这些检查保留在客户端中,才能让一次成功的抓包复现成为可重复的接入流程。