协议速查表
面向修改协议与排查抓包的紧凑参考。定义基于 scrcpy 4.1;协议没有独立协商版本,client/server 必须锁步。
一、连接建立顺序
启用哪些通道,就严格按以下顺序建立:
text
video? → audio? → control?Android 端实现见 DesktopConnection.open(),C 端配对见 sc_server_connect_to()。连接没有通道标签。
连接前导
| 数据 | 所在 socket | 条件 | 长度 |
|---|---|---|---|
| dummy byte | 第一条启用 socket | adb forward | 1 |
| device name | 第一条启用 socket | send_device_meta | 64 bytes |
设备名是固定字段,不是长度前缀字符串;客户端强制最后一字节为 \0。
二、媒体流
2.1 Stream header
text
offset size field
0 4 codec_id (u32 big-endian)| ID | ASCII | 含义 |
|---|---|---|
0x68323634 | h264 | H.264 |
0x68323635 | h265 | H.265 / HEVC |
0x00617631 | \0av1 | AV1 |
0x00767038 | \0vp8 | VP8 |
0x00767039 | \0vp9 | VP9 |
0x6f707573 | opus | Opus |
0x00616163 | \0aac | AAC |
0x666c6163 | flac | FLAC |
0x00726177 | \0raw | PCM S16LE |
0 | — | stream 主动禁用 |
1 | — | stream 配置错误 |
映射源见 demuxer.c。
2.2 Session header(仅视频)
text
offset size field
0 4 flags (u32 BE): bit31=session, bit0=client_resized
4 4 width (u32 BE)
8 4 height (u32 BE)最高 bit 为 1 时,整个 12 字节块就是 session,无 payload。
2.3 Media packet
text
offset size field
0 8 pts_and_flags (u64 BE)
bit62 config
bit61 key frame
low 61 bits PTS (microseconds)
8 4 payload_size (u32 BE)
12 N payload编码端见 Streamer.writeFrameMeta(),解码端见 sc_demuxer_recv_packet()。
三、ControlMessage:desktop → device
所有消息第 0 字节是 type。
| type | 名称 | type 后 payload |
|---|---|---|
| 0 | INJECT_KEYCODE | action:u8, keycode:u32, repeat:u32, meta:u32 |
| 1 | INJECT_TEXT | len:u32, utf8 |
| 2 | INJECT_TOUCH_EVENT | action:u8, pointer:u64, position, pressure:u16, action_button:u32, buttons:u32 |
| 3 | INJECT_SCROLL_EVENT | position, hscroll:i16, vscroll:i16, buttons:u32 |
| 4 | BACK_OR_SCREEN_ON | action:u8 |
| 5 | EXPAND_NOTIFICATION_PANEL | — |
| 6 | EXPAND_SETTINGS_PANEL | — |
| 7 | COLLAPSE_PANELS | — |
| 8 | GET_CLIPBOARD | copy_key:u8 |
| 9 | SET_CLIPBOARD | sequence:u64, paste:u8, len:u32, utf8 |
| 10 | SET_DISPLAY_POWER | on:u8 |
| 11 | ROTATE_DEVICE | — |
| 12 | UHID_CREATE | id:u16, vendor:u16, product:u16, name_len:u8, name, data_len:u16, report |
| 13 | UHID_INPUT | id:u16, data_len:u16, report |
| 14 | UHID_DESTROY | id:u16 |
| 15 | OPEN_HARD_KEYBOARD_SETTINGS | — |
| 16 | START_APP | len:u8, utf8 |
| 17 | RESET_VIDEO | — |
| 18 | CAMERA_SET_TORCH | on:u8 |
| 19 | CAMERA_ZOOM_IN | — |
| 20 | CAMERA_ZOOM_OUT | — |
| 21 | RESIZE_DISPLAY | width:u16, height:u16 |
| 22 | SCAN_FILE | len:u32, utf8 |
position 固定为 x:i32, y:i32, screen_width:u16, screen_height:u16。pressure 是 [0,1] 的 u16 定点数;scroll 是映射到 [-16,16] 的 i16 定点数。
完整 C enum/union 见 control_msg.h,Java parser 见 ControlMessageReader。
四、DeviceMessage:device → desktop
| type | 名称 | type 后 payload |
|---|---|---|
| 0 | CLIPBOARD | len:u32, utf8 |
| 1 | ACK_CLIPBOARD | sequence:u64 |
| 2 | UHID_OUTPUT | id:u16, len:u16, bytes |
写端见 DeviceMessageWriter,读端见 device_msg.c。
五、长度与特殊值
| 约束 | 值 |
|---|---|
| control message 最大值 | 256 KiB |
| device message 最大值 | 256 KiB |
| inject text 最大 UTF-8 字节 | 300 |
| clipboard 最大 UTF-8 字节 | 256 KiB - header |
| scan file path | 256 bytes |
| mouse pointer id | UINT64_MAX |
| generic finger id | UINT64_MAX - 1 |
| virtual pinch finger | UINT64_MAX - 2 |
| invalid clipboard sequence | 0 |
UTF-8 截断必须发生在 code point 边界。C 消息销毁时,inject text、clipboard、app name 和 scan path 需要释放。
六、改协议时的最小验证
- 双端 type 数值一致;
- 字段顺序、宽度、signedness 与字节序一致;
- 长度上限发送端和接收端一致;
- C ownership/destroy 分支已加入;
- droppable 语义不会破坏状态机;
- camera/display 模式分发允许新消息;
- C serialize test 与 Java reader test 都有 golden bytes;
- 反向消息还要覆盖 TCP partial read。