跳到主要内容

可视对讲业务最佳实践

本文档面向二次开发者,阐述构建稳定、可运维的可视对讲系统所需的核心概念、基本逻辑、端间交互模式与设计原则。关于 WebRTC 原理、DejaOS 设备 SDK 接入与服务端部署细节,请参阅 可视对讲概述 及同目录下的专题文档。


1. 核心概念

1.1 可视对讲是什么?

可视对讲,是让 门口设备手机 App(或室内机)之间实现实时 视频 + 语音 通话,并在通话过程中完成 远程开门 等门禁联动操作。

从系统角度看,一次完整的对讲过程包含三个阶段:

  1. 呼叫建立 — 一方发起请求,另一方收到通知并决定是否接听
  2. 实时通话 — 双方通过 WebRTC 传输音视频
  3. 会话结束 — 挂断、记录日志、可选的远程开门

核心分工一句话概括:

  • 信令负责"叫人"(协调双方进入同一会话)
  • RTC负责"通话"(传输音视频数据)
  • 业务平台负责"管人"(权限、日志、推送、开门)

1.2 系统中的四类角色

角色职责不参与
门口设备采集视频/音频、播放对方声音、触发呼叫、接收远程开门指令不负责推送通知、不负责权限管理
手机 App接收来电、发起呼叫、播放设备画面、远程开门不直接与设备建立业务通信(需经平台或 RTC 服务)
RTC 服务信令转发、SDP/ICE 协商、STUN/TURN 媒体中继不参与业务权限、不存储呼叫记录
业务平台设备-用户绑定、呼叫权限、推送通知、呼叫日志、远程开门鉴权不传输音视频数据
为什么需要业务平台?

RTC 服务只解决"怎么连上、怎么通话",不解决"谁有权呼叫谁、谁接听了、有没有开门"。这些业务逻辑必须由开发者自建的业务平台承担。

1.3 两个关键标识

可视对讲系统中有两个极易混淆的标识,必须在设计阶段明确区分:

标识用途谁生成生命周期
设备 RTC 标识设备在 RTC 服务中的唯一身份,用于 WebRTC 呼叫路由设备固件 / RTC 服务注册时分配与设备绑定,长期不变
业务设备 ID设备在业务平台(门禁系统)中的唯一身份业务平台分配与设备绑定,长期不变
会话 ID一次呼叫的全链路唯一标识呼叫发起方生成单次呼叫,通话结束后归档

最佳实践:

  • App 发起 WebRTC 呼叫时,目标地址必须使用 设备 RTC 标识,而非业务设备 ID
  • 设备上线后,应将 RTC 标识同步至业务平台,供 App 查询
  • 会话 ID 必须在设备、App、业务平台三端保持一致,作为日志、推送、开门的关联键

1.4 媒体能力约束(DejaOS 设备)

DejaOS 设备与 Android 设备在媒体能力上存在差异,设计业务逻辑时必须考虑:

能力DejaOS 设备Android 设备 / 手机 App
视频单向(设备→App,App 只看不说)双向
音频双向双向
主动呼叫 App不支持(需经业务平台中转)支持

这意味着:当门口设备"主动找人"时,实际链路是 设备通知平台 → 平台推送 App → App 反向发起 WebRTC 呼叫,而非设备直接呼叫 App。详见 3.4 业务主叫与技术被叫


2. 核心数据模型

2.1 呼叫权限(白名单)

可视对讲不是"任意用户可呼叫任意设备",而是通过 设备-用户白名单 控制呼叫关系。

字段说明
设备 ID业务平台中的设备标识
用户 ID允许被该设备呼叫的用户
租户 / 组织多租户隔离

设计原则:

  • 设备端展示的可呼叫联系人,应从业务平台 实时拉取,不要硬编码在设备本地
  • 管理员在后台变更白名单后,设备下次查询即生效
  • 设备呼叫用户前,平台必须校验目标用户在白名单中

2.2 呼叫记录

每次呼叫(无论成功与否)都应写入审计日志:

字段说明
会话 ID全链路唯一标识
设备 RTC 标识设备在 RTC 服务中的身份
设备 ID / 名称业务设备信息
被叫用户 ID / 姓名用户快照(记录时的姓名,不随用户后续修改而变化)
呼叫方向设备→App 或 App→设备
呼叫结果见下表
发起时间 / 接通时间 / 结束时间时间戳
通话时长接通后的秒数
是否远程开门本次通话中是否执行了开门

呼叫结果状态建议统一为以下枚举:

状态含义
呼叫中已发起,等待接听
已接通双方建立音视频连接
未接听超时或用户未操作
超时超过等待时限
已拒绝用户主动拒接
忙线设备或用户正在其他通话中
已取消发起方主动取消
异常网络错误或其他异常中断

2.3 设备 RTC 标识同步

设备上线后,需要将 RTC 标识上报至业务平台。推荐流程:

设备启动 → 注册到 RTC 服务(获得/确认 RTC 标识)
→ 通过设备通信通道(MQTT 等)上报至业务平台
→ 业务平台存储,供 App 查询

App 发起呼叫前,从业务平台获取目标设备的 RTC 标识,而非自行构造或使用业务设备 ID 替代。


3. 端间交互架构

3.1 总体架构

                    ┌─────────────────────────────┐
│ RTC 服务 │
│ 信令 / SDP·ICE / STUN·TURN │
└──────┬──────────────┬───────┘
│ │
设备信令通道 │ │ App 信令通道
(TCP) │ │ (WebSocket)
│ │
┌─────────▼──┐ ┌──────▼──────┐
│ 门口设备 │ │ 手机 App │
│ (DejaOS) │ │ (WebRTC) │
└──────┬─────┘ └──────┬──────┘
│ 设备通信通道 │ HTTP
│ (MQTT 等) │ + 推送
┌──────▼──────────────────▼──────┐
│ 业务平台 │
│ 白名单 / 日志 / 推送 / 远程开门 │
└─────────────────────────────────┘

各层边界清晰,互不越权:

  • 设备与 App 之间的音视频,只经过 RTC 服务,不经过业务平台
  • 设备与 App 之间的业务信息(谁呼叫谁、呼叫结果),只经过业务平台,不经过 RTC 服务
  • 推送通知由业务平台发出,App 收到后自行连接 RTC 服务建立通话

3.2 两条信令通道

RTC 服务通常提供两种信令接入方式,对应不同终端:

终端信令协议典型端口说明
DejaOS 设备TCP(私有协议)如 6699设备 SDK 内置,开发者无需实现
App / H5 / AndroidWebSocket(私有协议)如 8443开发者按协议文档实现

两种通道连接的是同一个 RTC 服务,设备 RTC 标识是跨通道呼叫路由的关键。

3.3 设备通信通道

设备与业务平台之间,需要一条独立的通信通道(推荐 MQTT),用于:

  • 设备上报 RTC 标识
  • 设备查询可呼叫联系人列表
  • 设备发起/取消呼叫请求
  • 业务平台下发远程开门等指令

这条通道与 RTC 信令通道完全独立,各司其职。

3.4 业务主叫与技术被叫

在 DejaOS 及不少嵌入式门口机方案中,会经常出现一种看似"反直觉"的现象:业务上是设备在呼叫 App,但 WebRTC 链路上却是 App 在呼叫设备

理解这一点的关键是把两个层面分开:

层面谁发起做什么
业务层门口设备上报"我要呼叫某用户"(携带会话 ID、目标用户等信息)
媒体层手机 App以设备 RTC 标识为目标,向 RTC 服务发起 WebRTC 连接
业务视角:  设备 ──呼叫──→ App
技术视角: 设备 ←──WebRTC── App(App 为连接发起方,设备为被叫方)

为什么不矛盾?

  • 设备的"呼叫",本质是向业务平台声明一次会话意图,并触发 App 侧的来电通知
  • 真正的音视频通道,仍由 App 按 RTC 协议主动建立
  • 设备在 RTC 服务中通常以固定 RTC 标识注册在线,扮演稳定的被叫目标

设计时需要统一的认知:

  1. 呼叫日志的"方向" — 按业务意图记录(设备→App),不要按 WebRTC 连接发起方记录
  2. 推送的内容 — 必须携带会话 ID 与设备 RTC 标识,App 收到后才能反向建链
  3. 设备的 UI — 可以展示"正在呼叫张三",但底层并不直接向 App 拨号
  4. App 的 UI — 展示"来电",用户接听后 App 才发起 WebRTC
与其他设备的差异

Android 门口机或室内机通常可以直接与 App 建立双向 WebRTC,不一定需要"反向呼叫"模式。接入前应确认目标设备的 RTC 能力,再决定业务链路。


4. 基本流程

4.1 设备呼叫 App(主流程)

这是门口机场景最常见的链路,也是 DejaOS 方案的核心模式。

参与者: 访客 · 门口设备 · 业务平台 · 推送服务 · 手机 App · RTC 服务

Visitor     DoorDev     Platform    PushSvc     MobileApp   RTC
| | | | | |
| 按门铃/选人 | | | | |
|---------->| | | | |
| | 查询联系人 | | | |
| |---------->| | | |
| |<----------| | | |
| | 发起呼叫 | | | |
| |---------->| 校验+日志 | | |
| | | 发送推送 | | |
| | |---------->| | |
| |<----------| | | |
| | | | 推送到达 | |
| | | |---------->| |
| | | | | 校验会话 |
| | |<----------------------| |
| | |---------------------->| |
| | | | | 用户接听 |
| | | | | 反向呼叫 |
| | | | |---------->|
| |<==============================================>|
| | 音视频通话(经 RTC) | |
| | |<----------------------| 上报接通 |
| | | | | 通话中... |
| | |<----------------------| 上报结束 |

关键设计点:

  1. 设备不直接呼叫 App — 设备只向业务平台发起呼叫请求,由平台推送 App
  2. App 反向建立 WebRTC — App 收到推送后,以设备的 RTC 标识为目标,主动连接 RTC 服务
  3. 会话 ID 全链路一致 — 设备生成的会话 ID 经推送传递给 App,贯穿日志、RTC、开门全流程
  4. 推送前先校验 — App 收到推送后,应先向业务平台确认会话仍为"呼叫中",再振铃/跳转,避免过期推送误触发

4.2 App 呼叫设备(主动查看)

适用于管理员远程查看门口画面:

参与者: 手机 App · 业务平台 · RTC 服务 · 门口设备

MobileApp   Platform    RTC         DoorDev
| | | |
| 生成会话ID | | |
| 上报呼叫 | | |
|---------->| | |
| WebRTC 呼叫 | | |
|---------------------->| |
| |---------->|
|<==================================>|
| 音视频通话 |
| 上报接通 | | |
|---------->| | |
| 上报结束 | | |
|---------->| | |

与设备主叫的区别:

  • App 是 WebRTC 的主动发起方,无需推送
  • 业务平台只写日志,不参与媒体建立
  • 设备侧收到 RTC 来电回调后,按策略自动接听或展示接听 UI

4.3 远程开门

远程开门是对讲场景中最敏感的操作,必须与会话状态严格绑定:

参与者: 手机 App · 业务平台 · 门口设备

MobileApp   Platform    DoorDev
| | |
| 远程开门 | |
| (会话ID) | |
|---------->| |
| | 校验会话 |
| | 校验用户 |
| | 校验设备 |
| | 校验已接通 |
| | 执行开门 |
| |---------->|
| | 记录审计 |
|<----------| |

安全原则:

  • 已接通 的会话允许远程开门
  • 必须校验请求用户与会话中的被叫用户一致
  • 必须校验请求设备与会话中的设备一致
  • 开门结果写入呼叫日志,便于事后审计

4.4 取消呼叫

设备或 App 在对方未接听前取消呼叫时:

参与者: 发起方 · 业务平台 · 推送服务 · 接收方 App

Initiator   Platform    PushSvc     Receiver
| | | |
| 取消呼叫 | | |
| (会话ID) | | |
|---------->| | |
| | 更新日志 | |
| | 静默推送 | |
| |---------->| |
| | | 取消到达 |
| | |---------->|
| | | | 停止振铃
| | | | 关闭页面

最佳实践:

  • 取消通知应使用 静默消息(不展示通知栏),而非普通推送通知,避免 App 已处理来电后仍弹出通知
  • 仅当会话状态仍为"呼叫中"时才更新为"已取消"
  • App 收到取消消息后,应立即停止振动、关闭来电界面、释放预建连接

5. 会话生命周期

一次完整的对讲会话,从未发起到最终归档,经历以下状态流转:

                    ┌──────────┐
│ 未发起 │
└────┬─────┘
│ 发起呼叫

┌──────────┐ 超时/取消 ┌──────────┐
┌────│ 呼叫中 │─────────────────→│ 已取消/超时│
│ └────┬─────┘ └──────────┘
│ │ 对方接听
│ ▼
│ ┌──────────┐ 挂断/异常 ┌──────────┐
│ │ 已接通 │─────────────────→│ 已结束 │
│ └────┬─────┘ └──────────┘
│ │ 远程开门(可选)
│ ▼
│ ┌──────────┐
│ │ 已开门 │ → 继续通话或挂断
│ └──────────┘

│ 拒接/忙线

┌──────────┐
│ 已拒绝/忙线│
└──────────┘

各端职责:

状态变更谁触发谁记录
→ 呼叫中发起方业务平台写日志
→ 已接通接听方业务平台更新日志
→ 已结束任意一方挂断业务平台更新日志 + 计算时长
→ 已取消发起方取消业务平台更新日志 + 通知接收方
→ 已拒绝/忙线/超时接收方或超时机制业务平台更新日志

6. 推送通知设计

推送是对讲系统中"叫人"的关键环节。App 不可能长期保持与业务平台的实时连接(尤其在进程被杀后),因此需要借助 第三方推送服务(如极光推送、Firebase 等)将来电信息送达手机。

6.1 为什么需要第三方推送

问题说明
App 进程被杀无法维持 WebSocket / 长连接,业务平台无法直接通知 App
系统省电策略Android / iOS 会限制后台进程和网络活动
实时性要求门口机呼叫需要在数秒内触达用户

第三方推送服务通过厂商通道(如华为、小米、OPPO 等)或系统级通道(如 APNs、FCM),即使用户 App 已完全关闭,也能将消息推送到设备。

接入要点:

  • 用户登录 App 后,将其用户 ID 绑定为推送服务的 别名(Alias),业务平台按用户 ID 推送
  • 用户登出后,应解除绑定或忽略推送,避免给未登录用户响铃
  • 来电推送设置 短 TTL(如 60 秒),过期未达则丢弃

6.2 两种 App 运行状态

同一条来电推送,因 App 所处状态不同,处理路径也不同:

状态 A:App 在前台或后台存活

推送到达 → 第三方 SDK 回调(通知到达 / 通知点击)
→ App 业务层收到事件
→ 校验会话状态 → 振铃 / 跳转来电页
  • App 进程仍在,推送 SDK 可直接通过事件回调将消息传递给 App 业务代码
  • 前台:可立即振铃、弹出来电界面
  • 后台:通知栏展示来电通知;若 App 仍存活,可同时触发振动;用户点击通知后进入来电页

状态 B:App 已完全关闭(冷启动)

推送到达 → 系统展示通知栏
→ 用户点击通知 → 启动 App
→ 从推送 SDK 缓存中恢复未处理的推送
→ 校验会话状态 → 跳转来电页
  • App 进程不存在,推送 SDK 无法立即回调 JS / 业务层
  • 系统先在通知栏展示来电,用户点击通知后才启动 App
  • App 启动后,必须从推送 SDK 的 待处理消息队列 中取出推送内容,否则来电信息丢失
  • 若用户未点击通知、App 未被拉起,则只能依赖通知栏 + 振动提醒,无法自动弹出来电页
┌─────────────────────────────────────────────────────────┐
│ 推送到达 │
└────────────────────┬────────────────────────────────────┘

┌──────────▼──────────┐
│ App 进程是否存活? │
└──────────┬──────────┘

┌───────────┴───────────┐
│ 是 │ 否
▼ ▼
SDK 事件回调 系统通知栏展示
直接送达业务层 用户点击后冷启动 App
可立即振铃/跳转 从缓存恢复推送再处理

最佳实践:

  • 两种路径必须都能正确处理来电,不能假设 App 一定在运行
  • 冷启动场景下,推送 payload 应同时写入 本地持久化缓存,作为 SDK 队列的兜底
  • App 每次切回前台时,主动检查是否有未处理的待处理推送

6.3 两种推送消息类型

业务平台应区分 来电通知取消通知,使用不同的推送形态:

类型用途推送形态是否展示通知栏
通知消息设备发起呼叫,通知 App 有来电通知栏推送(Notification)
自定义消息设备取消呼叫,通知 App 停止振铃静默消息(Custom Message)

为什么取消要用静默消息?

  • 取消是控制指令,不是给用户看的信息
  • 若用通知栏推送,厂商通道可能只展示通知而不回调 App 业务层,导致 App 已处理来电后仍在振铃
  • 静默消息直达 App 业务代码,可立即停止振动、关闭来电页

6.4 来电推送内容

要素建议
推送目标被叫用户的唯一标识(与 App 登录身份绑定)
携带信息会话 ID、设备 RTC 标识、业务设备 ID、设备名称
有效期短 TTL(如 60 秒),过期未达则丢弃
到达后App 先向业务平台校验会话仍为"呼叫中",再振铃/跳转

6.5 取消推送内容

要素建议
推送类型静默自定义消息,不要使用通知栏推送
携带信息会话 ID + 取消标识
到达后App 匹配当前来电会话,停止振铃并关闭界面

6.6 App 端推送处理原则

  • 先校验再振铃 — 收到推送后向业务平台确认会话仍为"呼叫中",过期推送直接丢弃
  • 去重 — 同一会话的重复推送不重复跳转
  • 补跳 — 用户不在来电页时,重复推送或点击通知应能补跳一次
  • 冷启动兜底 — App 被杀后恢复时,从 SDK 队列 + 本地缓存双重恢复未处理来电
  • 登出保护 — 用户未登录时不处理任何对讲推送
  • 取消优先 — 取消消息即使在 App 后台也应立即生效,清除振铃和通知

7. 设计原则与最佳实践

7.1 架构原则

  1. 三层分离 — RTC 管通话,业务平台管权限与审计,终端管交互。不要在 RTC 层做业务逻辑,不要在业务层传音视频
  2. 双通道独立 — 设备与平台的业务通信(MQTT 等)和终端与 RTC 服务的媒体信令(TCP/WebSocket)是两条独立通道,不要混用
  3. 标识分离 — 设备 RTC 标识、业务设备 ID、会话 ID 三者职责不同,全链路严格区分

7.2 设备侧

  1. 联系人实时拉取 — 设备展示的可呼叫列表从业务平台获取,不硬编码
  2. RTC 标识上报 — 设备上线后必须将 RTC 标识同步至业务平台
  3. 有线网络优先 — 可视对讲对时延敏感,有线网络稳定性优于 WiFi
  4. Worker 线程驱动 — 网络、媒体、对讲模块在独立线程中循环驱动,UI 线程只做交互
  5. 会话状态机 — 以会话 ID 为单位管理本地状态,支持超时挂断、忙线拒接

7.3 业务平台

  1. 呼叫前校验 — 白名单 + 用户在线状态,缺一不可
  2. 全程审计 — 每次呼叫都写日志,无论成功与否
  3. 远程开门绑定会话 — 必须已接通 + 用户/设备/会话三重匹配
  4. 取消通知用静默消息 — 避免 App 已处理后仍弹通知
  5. 幂等上报 — 通话结束上报应防重复,同一 session 只接受一次最终结果

7.4 App 端

  1. 推送先校验再振铃 — 收到推送后确认会话仍为"呼叫中"
  2. 兼顾两种 App 状态 — 前台/后台存活与完全关闭冷启动,必须都能正确处理来电
  3. 资源成对释放 — 任何结束路径都必须:关闭 WebRTC 连接 + 上报通话结果 + 停止振动
  4. 超时策略 — 主叫和被叫分别设置合理的等待超时(建议 20~30 秒)
  5. 麦克风权限前置 — 进入通话页第一时间检查权限,拒绝时降级为仅接收音频
  6. 两阶段来电 — 振铃阶段可预建信令连接,用户确认接听后再建立完整 WebRTC 媒体通道

7.5 DejaOS 特有模式

  1. 业务主叫、技术被叫 — 设备上报呼叫意图,App 反向建立 WebRTC,参见 3.4 节
  2. 视频单向 — App 端 WebRTC 参数设为仅接收视频(recvonly),仅发送音频(sendrecv)
  3. 先打通最小链路 — live 模式 + 主码流 + 双向音频,验证通过后再叠加 DataChannel、抓拍等扩展

8. 常见问题

现象可能原因排查方向
信令通但无画面STUN/TURN / UDP 端口未放行检查防火墙、ICE 连通性
App 呼叫设备无响应使用了业务设备 ID 而非 RTC 标识确认 App 使用的 peer ID
设备呼叫 App 无推送用户不在线 / 推送 alias 未绑定检查用户登录态与推送配置
推送到了但无法接听WebSocket 地址或端口错误核对 App 信令服务地址
远程开门失败会话未处于"已接通"状态检查接通上报时序
设备联系人列表为空白名单未配置检查设备-用户绑定
通话结束无日志结束上报未触发检查 App 资源释放逻辑
取消后 App 仍在振铃取消消息用了通知栏推送改用静默自定义消息
App 被杀后点击通知无反应冷启动未恢复推送缓存检查 SDK 待处理队列与本地持久化
回声严重终端距离过近调试时拉开距离,确认 AEC 开启

9. 最佳实践摘要

  1. 三层分离:RTC 管通话,平台管权限与审计,终端管交互
  2. 标识分离:RTC 标识、业务设备 ID、会话 ID 各司其职,不可混用
  3. 区分业务主叫与技术被叫:设备发起业务呼叫,App 发起 WebRTC 连接
  4. 白名单驱动:谁可以呼叫谁,由业务平台配置,设备实时拉取
  5. 推送兼顾冷启动与热启动,来电用通知、取消用静默消息
  6. 远程开门必须绑定已接通会话,并留审计日志
  7. 任何结束路径都要:释放连接 + 上报结果 + 停止振动
  8. 设备上线必须同步 RTC 标识
  9. 先打通最小链路,再叠加扩展能力
  10. 全程审计,每次呼叫无论成败都写日志

10. 延伸阅读