remote/common/src/lib.rs
dev-team 7dbb04da17 feat(hud): B1 基线指标——抖动/帧间隔p95/丢包率/解码失败计数 + 测试脚本
- 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: 三场景基线测试脚本
2026-10-23 09:47:09 +08:00

104 lines
3.6 KiB
Rust
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.

//! 通用协议与帧封装:控制消息(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()))
}