依据 Gitea v1.27.3 modules/charset/escape_stream.go 的判定规则 (analyzeDetectResults:仅当左右相邻字符都不是非 ASCII 时才标记, 且 _common 表恒定生效),把说明文档中会被标记的全角标点 (,:;?!() 等)改为半角,并保证成对括号宽度一致。 中文语境中的全角标点(左右均为中文)保持原样,不动代码与契约语义。 同时删除临时探针目录 _probe/(仅用于确认 Gitea 判定规则)。
132 lines
4.0 KiB
Markdown
132 lines
4.0 KiB
Markdown
# contracts/websocket-keypoints.md - 关键点传输契约 v0.1.0
|
|
|
|
Android 辅助 App(发送端)-> Windows 客户端(接收端)。
|
|
服务端不参与实时链路,但 T-WEB-006 预处理产出的序列必须与此结构同构。
|
|
|
|
## 1. 端点
|
|
|
|
```
|
|
ws://<windows-host>:8765/pose
|
|
```
|
|
|
|
- Windows 客户端监听,Android App 主动连接。
|
|
- 子协议:`opendance.pose.v1`(`Sec-WebSocket-Protocol`)
|
|
- 二进制帧传输关键点,文本帧传输控制消息(JSON)。
|
|
- 默认 30 FPS;发送端允许丢帧,接收端必须按 `frame_id` 检测丢包并插值补齐。
|
|
|
|
## 2. 二进制帧:239 字节(冻结)
|
|
|
|
**小端序(little-endian)**。
|
|
|
|
```
|
|
偏移 长度 字段 类型 说明
|
|
0 2 magic u16 LE 固定 0x4F44(ASCII "OD")
|
|
2 1 version u8 固定 0x01
|
|
3 1 flags u8 bit0 = 镜像画面(mirrored)
|
|
bit1 = 本帧含低置信度点(<0.5)
|
|
bit2 = 上半身模式(仅 0..22 有效)
|
|
bit3-7 保留,必须为 0
|
|
4 4 frame_id u32 LE 单调递增,从 0 开始
|
|
8 231 keypoints 33 x 7B 见下
|
|
--
|
|
合计 239
|
|
```
|
|
|
|
### 单个关键点(7 字节,`i` 为 0..32)
|
|
|
|
```
|
|
偏移 长度 字段 类型 换算
|
|
+0 2 x i16 LE x_norm = x / 32767.0
|
|
+2 2 y i16 LE y_norm = y / 32767.0
|
|
+4 2 z i16 LE z_norm = z / 32767.0
|
|
+6 1 conf u8 visibility = conf / 255.0
|
|
```
|
|
|
|
- 点索引与坐标语义见 `skeleton-topology.md`。
|
|
- 编码:`x = clamp(round(x_norm * 32767), -32768, 32767)`, `y`、`z` 同理。
|
|
- 低置信度点(`conf < 128`)坐标无意义,接收端必须插值,不得直接使用。
|
|
- `i16` 为有符号数:`0x8000` 表示 `-32768`。
|
|
|
|
### 参考实现(GDScript 风格伪码)
|
|
|
|
```gdscript
|
|
func decode(buf: PackedByteArray) -> Dictionary:
|
|
assert(buf.size() == 239)
|
|
var magic := buf.decode_u16(0)
|
|
assert(magic == 0x4F44 and buf.decode_u8(2) == 1)
|
|
var flags := buf.decode_u8(3)
|
|
var frame_id := buf.decode_u32(4)
|
|
var pts := []
|
|
for i in 33:
|
|
var o := 8 + i * 7
|
|
pts.append({
|
|
"x": float(buf.decode_s16(o)) / 32767.0,
|
|
"y": float(buf.decode_s16(o + 2)) / 32767.0,
|
|
"z": float(buf.decode_s16(o + 4)) / 32767.0,
|
|
"conf": float(buf.decode_u8(o + 6)) / 255.0,
|
|
})
|
|
return {"flags": flags, "frame_id": frame_id, "points": pts}
|
|
```
|
|
|
|
## 3. 文本控制帧(JSON, UTF-8)
|
|
|
|
### 3.1 `hello` - Android -> Windows,连接后立即发送
|
|
|
|
```json
|
|
{
|
|
"type": "hello",
|
|
"protocol": "opendance.pose.v1",
|
|
"device": "Pixel 8",
|
|
"appVersion": "0.1.0",
|
|
"cameraFacing": "front",
|
|
"targetFps": 30,
|
|
"resolution": "640x480"
|
|
}
|
|
```
|
|
|
|
### 3.2 `welcome` - Windows -> Android
|
|
|
|
```json
|
|
{ "type": "welcome", "protocol": "opendance.pose.v1", "sessionId": "uuid", "expectedFps": 30 }
|
|
```
|
|
|
|
### 3.3 `config` - Windows -> Android,运行中调整
|
|
|
|
```json
|
|
{ "type": "config", "mirror": true, "targetFps": 30, "upperBodyOnly": false, "resolution": "640x480" }
|
|
```
|
|
|
|
### 3.4 `stats` - Android -> Windows,每秒一次
|
|
|
|
```json
|
|
{ "type": "stats", "fps": 29.6, "dropped": 3, "latencyMs": 42, "confidenceAvg": 0.93 }
|
|
```
|
|
|
|
### 3.5 `bye` - 双向
|
|
|
|
```json
|
|
{ "type": "bye", "reason": "user_stop" }
|
|
```
|
|
|
|
## 4. JSON 调试帧(可选,仅开发期)
|
|
|
|
```json
|
|
{
|
|
"type": "pose",
|
|
"protocol": "opendance.pose.v1",
|
|
"frameId": 1024,
|
|
"timestampMs": 1730000000000,
|
|
"flags": { "mirrored": false, "upperBodyOnly": false },
|
|
"points": [[0.51, 0.22, -0.03, 0.99], "...共 33 项,顺序固定 [x, y, z, conf]"]
|
|
}
|
|
```
|
|
|
|
生产链路必须使用二进制帧;JSON 帧仅用于联调,接收端可选择性支持。
|
|
|
|
## 5. 兼容性规则
|
|
|
|
- `version` 不同的大版本接收端必须拒绝并在 `bye.reason` 中说明。
|
|
- `flags` 新增位不得改变既有位语义;旧接收端必须忽略未知位。
|
|
- 33 点顺序一旦冻结不得插入或重排;新增点只能追加新 `version`。
|
|
- 服务端 `motions.keypoint_format` 字段取值 `mediapipe33@0.1.0` 对应本契约。
|