Files
SecMPS/owl_api_research.md
2026-05-15 23:22:48 +08:00

595 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# OwlGoWVP开源视频监控管理平台 API 调研报告
> 调研目标:为 Vol.Pro 后端对接 Owl 提供技术方案依据
> 仓库地址https://github.com/gowvp/owl
> 调研日期2026-05-06
---
## 一、API 认证方式
Owl 采用 **JWT Token** 认证机制,所有业务 API除登录、健康检查、Webhook 回调外)均需在请求头中携带 Token。
### 1.1 登录获取 Token
- **URL**: `POST /login`
- **请求参数**:
```json
{
"data": "<RSA加密后的JSON字符串>"
}
```
- 加密前内容示例:`{"username":"admin","password":"admin"}`
- 公钥获取:`GET /login/key`,返回 Base64 编码的 RSA 公钥PEM 格式)
- **响应示例**:
```json
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"user": "admin"
}
```
- **Token 有效期**: 3 天
- **使用方式**: 请求头携带 `Authorization: Bearer <token>`
### 1.2 修改凭据
- **URL**: `PUT /users`
- **说明**: 需先校验旧密码,同样使用 RSA 加密传输
---
## 二、设备管理 API
> 设备类型支持:`GB28181`、`ONVIF`、`RTMP`、`RTSP`
### 2.1 设备列表查询
- **URL**: `GET /devices`
- **认证**: 需 JWT
- **请求参数** (Query):
| 参数 | 类型 | 说明 |
|------|------|------|
| `page` | int | 页码,默认 1 |
| `size` | int | 每页大小 |
| `key` | string | 关键词模糊搜索(名称/国标编号) |
- **响应**:
```json
{
"items": [
{
"id": "gb_34020000001320000001",
"type": "GB28181",
"device_id": "34020000001320000001",
"name": "NVR-01",
"transport": "UDP",
"stream_mode": 1,
"ip": "192.168.1.100",
"port": 5060,
"is_online": true,
"registered_at": "2026-05-06T10:00:00Z",
"keepalive_at": "2026-05-06T10:05:00Z",
"keepalives": 30,
"expires": 3600,
"channels": 4,
"address": "192.168.1.100:5060",
"password": "",
"username": "",
"ext": {}
}
],
"total": 100
}
```
### 2.2 设备详情
- **URL**: `GET /devices/:id`
- **响应**: 单条 Device 对象(同上)
### 2.3 添加设备
- **URL**: `POST /devices`
- **认证**: 需 JWT
- **请求体**:
```json
// GB28181 设备
{
"type": "GB28181",
"device_id": "34020000001320000001",
"name": "摄像头1",
"password": "123456"
}
// ONVIF 设备
{
"type": "ONVIF",
"ip": "192.168.1.100",
"port": 80,
"username": "admin",
"password": "12345",
"name": "ONVIF摄像头"
}
```
- **说明**: GB28181 设备实际注册靠设备主动 SIP 注册,此接口用于预录入设备信息
### 2.4 修改设备
- **URL**: `PUT /devices/:id`
- **请求体** (`EditDeviceInput`):
| 字段 | 类型 | 说明 |
|------|------|------|
| `device_id` | string | 20 位国标编号 |
| `name` | string | 设备名称 |
| `password` | string | 注册密码 |
| `stream_mode` | int | 0:UDP, 1:TCP_PASSIVE, 2:TCP_ACTIVE |
| `username` | string | ONVIF 用户名 |
| `ip` | string | ONVIF IP |
| `port` | int | ONVIF 端口 |
### 2.5 删除设备
- **URL**: `DELETE /devices/:id`
### 2.6 查询设备目录(刷新通道)
- **URL**: `POST /devices/:id/catalog`
- **说明**: 向 GB28181 设备发送 Catalog 查询指令,拉取通道列表
- **响应**: `{"msg": "ok"}`(异步执行)
### 2.7 ONVIF 设备发现
- **URL**: `GET /onvif/discover`
- **说明**: SSE 流式返回发现的 ONVIF 设备
---
## 三、通道管理 API
### 3.1 通道列表查询
- **URL**: `GET /channels`
- **认证**: 需 JWT
- **请求参数** (Query):
| 参数 | 类型 | 说明 |
|------|------|------|
| `page` | int | 页码 |
| `size` | int | 每页大小 |
| `did` | string | 父设备 ID |
| `device_id` | string | 国标编码 |
| `key` | string | 名称/国标编码模糊搜索ID 精确搜索 |
| `is_online` | string | 是否在线过滤 |
| `type` | string | 通道类型GB28181/ONVIF/RTMP/RTSP |
| `app` | string | 应用名 |
| `stream` | string | 流 ID |
- **响应**:
```json
{
"items": [
{
"id": "gb_34020000001320000001_34020000001320000001",
"did": "gb_34020000001320000001",
"device_id": "34020000001320000001",
"channel_id": "34020000001320000001",
"name": "通道01",
"ptztype": 1,
"is_online": true,
"is_playing": false,
"type": "GB28181",
"app": "rtp",
"stream": "gb_34020000001320000001_34020000001320000001",
"has_recording": true,
"config": {}
}
],
"total": 100
}
```
### 3.2 设备与通道关联列表
- **URL**: `GET /devices/channels`
- **说明**: 返回设备列表,每个设备包含 `children`(通道数组),并按在线状态排序
- **响应**:
```json
{
"items": [
{
"id": "gb_xxx",
"name": "NVR-01",
"is_online": true,
"children": [
{
"id": "gb_xxx_xxx",
"name": "通道01",
"is_online": true,
"has_recording": true
}
]
}
],
"total": 10
}
```
### 3.3 添加通道RTMP/RTSP
- **URL**: `POST /channels`
- **请求体** (`AddChannelInput`):
```json
{
"type": "RTMP",
"name": "推流通道1",
"device_id": "",
"device_name": "",
"app": "live",
"stream": "stream01",
"config": {
"is_auth_disabled": false,
"session": "",
"media_server_id": "",
"push_addr": ""
}
}
```
### 3.4 修改通道
- **URL**: `PUT /channels/:id`
- **请求体** (`EditChannelInput`):
| 字段 | 类型 | 说明 |
|------|------|------|
| `device_id` | string | 国标编码 |
| `name` | string | 通道名称 |
| `ptztype` | int | 云台类型 |
| `is_online` | bool | 是否在线 |
| `ext` | object | 扩展属性 |
| `app` | string | 应用名RTMP/RTSP |
| `stream` | string | 流 ID |
| `config` | StreamConfig | 流配置 |
### 3.5 删除通道
- **URL**: `DELETE /channels/:id`
- **限制**: 仅允许删除 RTMP/RTSP 类型通道
---
## 四、实时流地址获取 API
### 4.1 获取播放地址
- **URL**: `POST /channels/:id/play`
- **认证**: 需 JWT
- **说明**: 根据通道类型自动处理拉流逻辑
- **GB28181**: Owl 通过 SIP 邀请设备推流到 ZLM自动开启 RTP Server
- **RTMP**: 检查是否已推流,直接从 ZLM 获取播放地址
- **RTSP**: 通过 ZLM 添加流代理AddStreamProxy再返回地址
- **ONVIF**: 直接从 ZLM 获取播放地址
- **响应** (`playOutput`):
```json
{
"app": "rtp",
"stream": "gb_34020000001320000001_34020000001320000001",
"items": [
{
"label": "ZLM",
"ws_flv": "ws://host:port/proxy/sms/rtp/gb_xxx.live.flv",
"http_flv": "http://host:port/proxy/sms/rtp/gb_xxx.live.flv",
"rtmp": "rtmp://host:port/rtp/gb_xxx",
"rtsp": "rtsp://host:port/rtp/gb_xxx",
"webrtc": "webrtc://host:port/proxy/sms/index/api/webrtc?app=rtp&stream=gb_xxx&type=play",
"hls": "http://host:port/proxy/sms/rtp/gb_xxx/hls.fmp4.m3u8"
}
]
}
```
### 4.2 停止播放
- **URL**: `POST /channels/:id/stop`
- **说明**: 幂等操作,始终返回成功
- GB28181: 发送 SIP BYE + 关闭 ZLM RTP Server
- ONVIF/RTSP/RTMP: 调用 ZLM `close_streams` 关闭流
- **响应**: `{"msg": "ok"}`
### 4.3 快照相关
- **刷新快照**: `POST /channels/:id/snapshot`
- 请求体可指定 `within_seconds`(秒内有效则返回缓存)和 `url`(指定取流地址)
- 响应: `{"link": "http://host/channels/:id/snapshot?token=xxx"}`
- **获取快照图片**: `GET /channels/:id/snapshot?token=xxx`
- 直接返回 JPEG 图片流
---
## 五、回放流地址获取 API云录像
Owl 的录像回放功能由 Owl 自身管理(非设备 SD 卡录像),底层依赖 ZLM 的 MP4 录制。
### 5.1 查询录像列表
- **URL**: `GET /recordings`
- **认证**: 需 JWT
- **请求参数** (Query):
| 参数 | 类型 | 说明 |
|------|------|------|
| `page` | int | 页码 |
| `size` | int | 每页大小 |
| `cid` | string | 通道 ID |
| `app` | string | 应用名 |
| `stream` | string | 流 ID |
| `start_ms` | int64 | 开始时间戳(毫秒) |
| `end_ms` | int64 | 结束时间戳(毫秒) |
- **响应**:
```json
{
"items": [
{
"id": 1,
"cid": "gb_xxx_xxx",
"app": "rtp",
"stream": "gb_xxx_xxx",
"started_at": "2026-05-06T10:00:00Z",
"ended_at": "2026-05-06T10:05:00Z",
"duration": 300.5,
"path": "/recordings/rtp/gb_xxx_xxx/20260506/xxx.mp4",
"size": 10485760
}
],
"total": 50
}
```
### 5.2 获取时间轴
- **URL**: `GET /recordings/timeline?cid=xxx&start_ms=xxx&end_ms=xxx`
- **响应**:
```json
{
"items": [
{
"id": 1,
"start_ms": 1714982400000,
"end_ms": 1714982700000,
"duration": 300.5
}
]
}
```
### 5.3 获取月度统计
- **URL**: `GET /recordings/monthly?cid=xxx&year=2026&month=5`
- **响应**:
```json
{
"year": 2026,
"month": 5,
"days": 31,
"has_video": "101010..." // 每天是否有录像,'1'有/'0'无
}
```
### 5.4 HLS 回放播放
- **URL**: `GET /recordings/channels/:cid/index.m3u8?start_ms=xxx&end_ms=xxx&token=xxx`
- **说明**: 动态生成 m3u8 播放列表,将指定时间范围内的多个 MP4 片段拼接为 HLS (VOD)
- **Content-Type**: `application/vnd.apple.mpegurl`
### 5.5 下载录像
- **URL**: `GET /recordings/:id/download`
- **说明**: 返回 MP4 文件流,支持 HTTP Range 请求(边下载边播放)
### 5.6 静态文件播放
- **URL**: `GET /static/recordings/{path}?token=xxx`
- **说明**: 直接访问 MP4/fMP4 文件,同样支持 Range 请求
---
## 六、云台控制 API
### 6.1 PTZ 控制
- **URL**: `POST /channels/:id/ptz/control`
- **认证**: 需 JWT
- **请求体** (`ptzControlInput`):
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `action` | string | 是 | 动作类型:`continuous` / `stop` / `absolute` / `relative` / `preset` |
| `direction` | string | 否 | 方向continuous 时用):`up` / `down` / `left` / `right` / `zoom_in` / `zoom_out` |
| `speed` | float64 | 否 | 速度 0-1 |
| `x` | float64 | 否 | X 轴位置absolute/relative |
| `y` | float64 | 否 | Y 轴位置 |
| `zoom` | float64 | 否 | 缩放值 |
| `preset_id` | string | 否 | 预置位 ID |
| `preset_op` | string | 否 | 预置位操作:`goto` / `set` / `remove` |
- **当前实现限制**: 代码中**仅实现了 `continuous` 和 `stop`**,其他动作类型会返回错误
- **响应**:
```json
{
"msg": "PTZ 控制指令已发送"
}
```
- **注意**: 目前代码中直接调用 GB28181 Server 的 PTZControl 方法未区分协议类型ONVIF PTZ 支持标记为 TODO
---
## 七、设备状态查询 API
### 7.1 在线状态
Owl 没有单独的"设备状态查询"接口,设备状态包含在设备/通道列表查询中:
- **设备在线状态**: `GET /devices` 或 `GET /devices/:id` → `is_online`
- **通道在线状态**: `GET /channels` 或 `GET /devices/channels` → `is_online`
- **通道播放状态**: `is_playing`
### 7.2 设备模型关键状态字段
| 字段 | 说明 |
|------|------|
| `is_online` | 是否在线SIP 注册成功且心跳正常) |
| `registered_at` | 最近一次注册时间 |
| `keepalive_at` | 最近一次心跳时间 |
| `keepalives` | 心跳间隔(秒) |
| `expires` | 注册有效期(秒) |
| `address` | 设备网络地址(如 `192.168.1.100:5060` |
| `transport` | 信令传输协议(`UDP` / `TCP` |
| `channels` | 通道数量 |
### 7.3 注册与心跳机制SIP 层)
- **注册**: GB28181 设备主动向 Owl 的 SIP 端口(默认 5060发送 REGISTER 请求
- Owl 支持 Digest 鉴权,密码可设备单独配置或使用全局默认密码
- 注册成功后 Owl 自动查询 DeviceInfo 和 Catalog
- **心跳**: 设备按 `Keepalives` 间隔发送 MESSAGE 心跳
- Owl 通过 `keepalive_at` 和 `expires` 判断设备是否离线
- 例如:设备侧超时 3 秒 × 3 次,则约 9+x 秒收不到心跳认为离线
- **注销**: 设备发送 Expires=0 的 REGISTER 请求
> **重要**: 注册和心跳是 **SIP 协议层**自动完成的,没有对应的 HTTP API。Vol.Pro 只需通过 HTTP API 查询设备列表即可获知在线状态。
---
## 八、Owl 与 ZLMediaKit 联动机制
### 8.1 架构关系
```
摄像机/NVR --(SIP/RTP)--> Owl --(HTTP API/Webhook)--> ZLMediaKit
^
| (反向代理播放)
前端播放器 --(HTTP/WebSocket)---> Owl /proxy/sms/xxx
```
### 8.2 Owl 配置 ZLM 的 Hook 回调
Owl 启动时会自动连接 ZLM并通过 `SetServerConfig` 接口设置以下 Webhook 地址(前缀为 `http://{owl_host}:{owl_port}/webhook`
| Hook 接口 | 方法 | 说明 |
|-----------|------|------|
| `/webhook/on_server_started` | POST | ZLM 启动时触发Owl 将所有 RTMP 通道置为离线 |
| `/webhook/on_server_keepalive` | POST | ZLM 定时保活(默认 10sOwl 更新节点在线状态 |
| `/webhook/on_publish` | POST | 推流鉴权RTMP/RTSP/RTPOwl 校验是否允许推流 |
| `/webhook/on_play` | POST | 播放鉴权Owl 更新通道播放状态为 playing |
| `/webhook/on_stream_changed` | POST | 流注册/注销时触发Owl 根据录像模式决定是否启动录制 |
| `/webhook/on_stream_none_reader` | POST | 无人观看时触发Owl 根据录像模式决定是否关闭流 |
| `/webhook/on_stream_not_found` | POST | 播放不存在的流时触发Owl 触发按需拉流ONVIF/RTSP |
| `/webhook/on_rtp_server_timeout` | POST | RTP Server 超时未收到数据 |
| `/webhook/on_record_mp4` | POST | MP4 切片录制完成Owl 将录像信息入库 |
### 8.3 Owl 调用 ZLM 的 API
通过 `internal/core/sms/driver_zlm.go` 封装调用:
| ZLM API | Owl 封装方法 | 说明 |
|---------|-------------|------|
| `openRtpServer` | `OpenRTPServer` | 开启 RTP 收流端口GB28181 用) |
| `closeRtpServer` | `CloseRTPServer` | 关闭 RTP 端口 |
| `close_streams` | `CloseStreams` | 强制关闭流 |
| `addStreamProxy` | `AddStreamProxy` | 添加 RTSP 拉流代理 |
| `getSnap` | `GetSnapshot` | 获取流截图 |
| `startRecord` | `StartRecord` | 开始 MP4 录制 |
| `stopRecord` | `StopRecord` | 停止 MP4 录制 |
| `getServerConfig` | `GetServerConfig` | 获取配置(含端口信息) |
| `setServerConfig` | `SetServerConfig` | 设置配置(含 Hook 地址) |
| `restartServer` | `RestartServer` | 重启 ZLMrtc 端口变更时) |
### 8.4 播放地址生成规则
Owl 通过反向代理 `/proxy/sms/*path` 将所有播放请求转发到 ZLM前端无需直接访问 ZLM。
生成逻辑(`GetStreamLiveAddr`
| 协议 | URL 格式示例 |
|------|-------------|
| **WS-FLV** | `ws://owl_host/proxy/sms/{app}/{stream}.live.flv` |
| **HTTP-FLV** | `http://owl_host/proxy/sms/{app}/{stream}.live.flv` |
| **HLS(fMP4)** | `http://owl_host/proxy/sms/{app}/{stream}/hls.fmp4.m3u8` |
| **WebRTC** | `webrtc://owl_host/proxy/sms/index/api/webrtc?app={app}&stream={stream}&type=play` |
| **RTMP** | `rtmp://owl_host:1935/{app}/{stream}` |
| **RTSP** | `rtsp://owl_host:554/{app}/{stream}` |
### 8.5 联动流程示例GB28181 实时播放)
1. 前端调用 `POST /channels/:id/play`
2. Owl 向 ZLM 调用 `openRtpServer` 开启 UDP/TCP 收流端口
3. Owl 通过 SIP 发送 INVITE 给设备SDP 中携带 ZLM 的收流地址
4. 设备通过 RTP 向 ZLM 推流
5. ZLM 触发 `on_publish` → `on_stream_changed` Hook
6. Owl 返回包含播放地址的 JSON 给前端
7. 前端通过 `/proxy/sms/...` 播放流
8. 无人观看时ZLM 触发 `on_stream_none_reader`
9. Owl 判断无需录像 → 返回 `Close: true` → ZLM 关闭流
10. Owl 发送 SIP BYE 结束会话
---
## 九、其他相关 API
### 9.1 流媒体服务器管理
- `GET /media_servers` - 查询媒体服务器列表
- `PUT /media_servers/:id` - 修改媒体服务器配置
### 9.2 推流管理
- `GET /stream_pushs` - 查询推流列表(含 RTMP 推流地址)
- `POST /stream_pushs` - 添加推流
- `PUT /stream_pushs/:id` - 修改推流
- `DELETE /stream_pushs/:id` - 删除推流
### 9.3 事件/告警管理
- `GET /events` - 分页查询 AI 检测事件
- `GET /events/:id` - 事件详情
- `GET /events/image/*path` - 获取事件快照图片(无需认证)
### 9.4 配置管理
- `GET /configs/info` - 获取 SIP 等配置信息
- `PUT /configs/info/sip` - 修改 SIP 配置
### 9.5 系统接口
- `GET /health` - 健康检查
- `GET /app/metrics/api` - API 实时监控指标
- `GET /app/version/check` - 检查新版本
---
## 十、Vol.Pro 对接建议
### 10.1 对接方式
Vol.Pro 后端直接通过 **HTTP REST API** 调用 Owl无需处理 SIP 协议。
### 10.2 关键对接流程
```
1. 登录 Owl → 获取 JWT Token缓存
2. 定时同步设备列表 → GET /devices
3. 定时同步通道列表 → GET /channels 或 GET /devices/channels
4. 用户点击播放 → POST /channels/:id/play → 获取播放地址 → 前端播放
5. 用户停止播放 → POST /channels/:id/stop
6. 回放查询 → GET /recordings?cid=xxx&start_ms=xxx&end_ms=xxx
7. 回放播放 → GET /recordings/channels/:cid/index.m3u8?start_ms=xxx&end_ms=xxx
8. 云台控制 → POST /channels/:id/ptz/control
```
### 10.3 注意事项
1. **Token 管理**: JWT 有效期 3 天,建议在 Vol.Pro 中缓存并处理过期刷新
2. **播放地址代理**: 前端播放地址建议走 Owl 的 `/proxy/sms/...` 路径,无需暴露 ZLM 直接地址
3. **按需拉流**: GB28181 实时播放是按需拉流,首次播放可能有 1-3 秒延迟SIP 信令 + ZLM 准备)
4. **录像模式**: 通道支持 `always` / `ai` / `none` 三种录像模式,通过 `POST /channels/:id/record_mode` 设置
5. **PTZ 限制**: 当前 Owl 仅支持 `continuous`(持续移动)和 `stop`(停止),预置位等功能未完整实现
6. **心跳与注册**: 不要尝试通过 HTTP 模拟设备注册,这是 SIP 层自动完成的
7. **ONVIF PTZ**: README 中标记为"未支持"`[ ] ONVIF PTZ control support`
---
*报告整理完毕,供 Vol.Pro 后端架构设计参考。*