- common: Pong 扩展 lost/sent(agent 发送方向 QUIC 计数,经交叉评审项) - agent: Pong 附带 conn.stats().path 丢包统计 - viewer: Stats 加 GapRing(64)/jitter EMA/dec_fail;HUD 新增 JIT/P95/LOSS/DECFAIL 四行,% 字形 - tools/bench.md: 三场景基线测试脚本
104 lines
3.6 KiB
Rust
104 lines
3.6 KiB
Rust
//! 通用协议与帧封装:控制消息(bincode + 长度前缀)与视频帧格式。
|
||
|
||
use anyhow::Result;
|
||
use serde::{Deserialize, Serialize};
|
||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||
|
||
/// 控制通道消息(双向)
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub enum ControlMsg {
|
||
/// 主控端认证(临时验证码,MVP 明文走 QUIC-TLS 加密通道)
|
||
Auth { code: String },
|
||
AuthOk { width: u32, height: u32 },
|
||
AuthFail(String),
|
||
KeyFrameReq,
|
||
Ping(u64),
|
||
/// t=主控端发送时刻(主控时钟),agent_ts=被控端收到时刻(被控时钟)
|
||
/// 两者配合可估计时钟偏移,进而用帧头 ts_ms 计算端到端延迟
|
||
/// lost/sent = agent 发送方向(视频下行)的 QUIC 丢包/发包累计计数(conn.stats().path)
|
||
Pong { t: u64, agent_ts: u64, lost: u64, sent: u64 },
|
||
Input(InputEvent),
|
||
}
|
||
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||
pub enum MouseButton {
|
||
Left,
|
||
Right,
|
||
Middle,
|
||
}
|
||
|
||
/// 输入事件。鼠标坐标为虚拟桌面归一化 0..=65535;
|
||
/// 按键为 Set-1 扫描码,extended 标记扩展键(方向键/Home 等,需要 EXTENDEDKEY 标志)。
|
||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||
pub enum InputEvent {
|
||
MouseMove { x: u16, y: u16 },
|
||
MouseDown(MouseButton),
|
||
MouseUp(MouseButton),
|
||
Wheel { dy: i32 },
|
||
KeyDown { scancode: u16, extended: bool },
|
||
KeyUp { scancode: u16, extended: bool },
|
||
}
|
||
|
||
/// 视频帧头(载荷为 LZ4 压缩的 BGRA 像素,按行紧凑排列)
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct VideoFrameHeader {
|
||
pub width: u32,
|
||
pub height: u32,
|
||
pub ts_ms: u64,
|
||
}
|
||
|
||
async fn read_u32le<R: AsyncReadExt + Unpin>(r: &mut R) -> Result<u32> {
|
||
let mut b = [0u8; 4];
|
||
r.read_exact(&mut b).await?;
|
||
Ok(u32::from_le_bytes(b))
|
||
}
|
||
|
||
/// 编码一条控制消息为「长度前缀 + bincode」
|
||
pub fn encode_control(msg: &ControlMsg) -> Vec<u8> {
|
||
let body = bincode::serialize(msg).expect("serialize control");
|
||
let mut out = Vec::with_capacity(body.len() + 4);
|
||
out.extend_from_slice(&(body.len() as u32).to_le_bytes());
|
||
out.extend_from_slice(&body);
|
||
out
|
||
}
|
||
|
||
pub async fn write_control<W: AsyncWriteExt + Unpin>(
|
||
w: &mut W,
|
||
msg: &ControlMsg,
|
||
) -> Result<()> {
|
||
w.write_all(&encode_control(msg)).await?;
|
||
Ok(())
|
||
}
|
||
|
||
pub async fn read_control<R: AsyncReadExt + Unpin>(r: &mut R) -> Result<ControlMsg> {
|
||
let len = read_u32le(r).await? as usize;
|
||
anyhow::ensure!(len < 1 << 20, "control frame too large: {len}");
|
||
let mut buf = vec![0u8; len];
|
||
r.read_exact(&mut buf).await?;
|
||
Ok(bincode::deserialize(&buf)?)
|
||
}
|
||
|
||
/// 编码视频帧:[总长 u32][头长 u32][bincode 头][LZ4 载荷]
|
||
pub fn encode_video_frame(header: &VideoFrameHeader, payload: &[u8]) -> Vec<u8> {
|
||
let hb = bincode::serialize(header).expect("serialize header");
|
||
let total = 4 + hb.len() + payload.len();
|
||
let mut out = Vec::with_capacity(total + 4);
|
||
out.extend_from_slice(&(total as u32).to_le_bytes());
|
||
out.extend_from_slice(&(hb.len() as u32).to_le_bytes());
|
||
out.extend_from_slice(&hb);
|
||
out.extend_from_slice(payload);
|
||
out
|
||
}
|
||
|
||
pub async fn read_video_frame<R: AsyncReadExt + Unpin>(
|
||
r: &mut R,
|
||
) -> Result<(VideoFrameHeader, Vec<u8>)> {
|
||
let total = read_u32le(r).await? as usize;
|
||
anyhow::ensure!(total < 256 << 20, "video frame too large: {total}");
|
||
let mut buf = vec![0u8; total];
|
||
r.read_exact(&mut buf).await?;
|
||
let hlen = u32::from_le_bytes(buf[0..4].try_into().unwrap()) as usize;
|
||
let header: VideoFrameHeader = bincode::deserialize(&buf[4..4 + hlen])?;
|
||
Ok((header, buf[4 + hlen..].to_vec()))
|
||
}
|