feat(viewer-core,android-ffi): v5 光标字段透传到 FFI(帧头 25→30 字节)
主控链路把协议帧头的光标坐标交给 Kotlin: - viewer-core::Frame 增 cursor: Option<(i16,i16)>,From 实现透传; 契约测试改为 Some((640,480)) 并断言透传——丢它=标记永不显示且丢法是静默的 - android-ffi 定长头 25→30:尾部追加 [has_cursor 1][x i16 LE 2][y i16 LE 2], 放末尾保证 Kotlin 既有偏移量(0/1/9/17/21)全部不移动 - None → has=0 且坐标字节恒 0(无意义);Some 分支显式写两坐标, 杜绝「Some 但忘写坐标」的编辑事故(文件头注释同步 30 字节布局) - R-5 同源检查:RustBridge.kt 的 FRAME_HEADER_LEN=25 与拆头偏移是本次协议变更的 同步改动点,Kotlin 侧在下一提交完成(同一循环、相邻提交)
This commit is contained in:
parent
ad46fc805b
commit
8eed1e20e1
@ -15,9 +15,13 @@
|
||||
//! 句柄约定:`nativeConnect` 返回 Box 裸指针(永不为 0)作为 jlong;
|
||||
//! `nativeDestroy` 释放。句柄释放后不得再使用,Kotlin 侧用置 0 保护。
|
||||
//!
|
||||
//! 帧返回约定:byte[],布局 = [1 标志][8 seq LE][8 ts LE][4 w LE][4 h LE][payload]。
|
||||
//! 定长头 25 字节;Kotlin 读 buf[0] 判断 keyframe(标志位复用为:0=P 帧, 1=IDR 帧,
|
||||
//! 会话结束则整个返回 null)。
|
||||
//! 帧返回约定:byte[],布局 = [1 标志][8 seq LE][8 ts LE][4 w LE][4 h LE]
|
||||
//! [1 has_cursor][2 cursorX i16 LE][2 cursorY i16 LE][payload]。
|
||||
//! 定长头 30 字节(v5 起;此前 25);Kotlin 读 buf[0] 判断 keyframe(标志位复用为:
|
||||
//! 0=P 帧, 1=IDR 帧,会话结束则整个返回 null)。
|
||||
//! has_cursor=0 时后 4 字节恒为 0 且**无意义**(指针隐藏/离屏/后端未实现,
|
||||
//! 语义单一来源在被控端——见 common::VideoFrameHeader::cursor);=1 时坐标为
|
||||
//! 采集画面内物理像素(0 ≤ x < w、0 ≤ y < h,被控端已折叠越界值)。
|
||||
|
||||
use common::InputEvent;
|
||||
use jni::objects::{JCharArray, JClass, JObject, JString};
|
||||
@ -26,8 +30,9 @@ use jni::JNIEnv;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use viewer_core::{ConnectConfig, Session};
|
||||
|
||||
/// 帧头长度:keyframe 标志(1) + seq(8) + ts(8) + w(4) + h(4)
|
||||
const FRAME_HEADER_LEN: usize = 25;
|
||||
/// 帧头长度:keyframe 标志(1) + seq(8) + ts(8) + w(4) + h(4) + has_cursor(1) + x(2) + y(2)
|
||||
/// (v5:25→30,尾部三字段是光标;放末尾保证 Kotlin 旧偏移量全部不移动)
|
||||
const FRAME_HEADER_LEN: usize = 30;
|
||||
|
||||
struct SessionBox {
|
||||
session: Arc<Session>,
|
||||
@ -168,6 +173,17 @@ pub extern "system" fn Java_com_verify_remote_RustBridge_nativeNextFrame<'local>
|
||||
buf[9..17].copy_from_slice(&f.ts_ms.to_le_bytes());
|
||||
buf[17..21].copy_from_slice(&f.width.to_le_bytes());
|
||||
buf[21..25].copy_from_slice(&f.height.to_le_bytes());
|
||||
// v5 光标三字段(偏移 25/26/28):None → has=0 且坐标字节保持 0(无意义)。
|
||||
// Option 展开成显式分支而不是 f.cursor.is_some() as u8——两处读同一个
|
||||
// Option,任何「Some 但忘写坐标」的编辑错误都会在这里编译期暴露。
|
||||
match f.cursor {
|
||||
Some((x, y)) => {
|
||||
buf[25] = 1;
|
||||
buf[26..28].copy_from_slice(&x.to_le_bytes());
|
||||
buf[28..30].copy_from_slice(&y.to_le_bytes());
|
||||
}
|
||||
None => buf[25] = 0,
|
||||
}
|
||||
buf[FRAME_HEADER_LEN..].copy_from_slice(&f.payload);
|
||||
match env.byte_array_from_slice(&buf) {
|
||||
Ok(a) => a.into_raw(),
|
||||
|
||||
@ -198,6 +198,10 @@ pub struct Frame {
|
||||
pub ts_ms: u64,
|
||||
pub seq: u64,
|
||||
pub keyframe: bool,
|
||||
/// v5:帧发送时刻的远端光标位置(采集画面内物理像素)。
|
||||
/// `None` = 隐藏/离屏/后端未实现(语义单一来源在被控端,见 `VideoFrameHeader::cursor`)。
|
||||
/// 桌面 viewer 不消费该字段(看着自己的鼠标),安卓据它画标记。
|
||||
pub cursor: Option<(i16, i16)>,
|
||||
pub payload: Vec<u8>,
|
||||
}
|
||||
|
||||
@ -209,6 +213,7 @@ impl From<(VideoFrameHeader, Vec<u8>)> for Frame {
|
||||
ts_ms: h.ts_ms,
|
||||
seq: h.seq,
|
||||
keyframe: h.keyframe,
|
||||
cursor: h.cursor,
|
||||
payload,
|
||||
}
|
||||
}
|
||||
@ -678,13 +683,15 @@ mod tests {
|
||||
ts_ms: 42,
|
||||
seq: 7,
|
||||
keyframe: true,
|
||||
cursor: None,
|
||||
cursor: Some((640, 480)),
|
||||
};
|
||||
let payload = vec![0u8; 16];
|
||||
let f: Frame = (h.clone(), payload.clone()).into();
|
||||
assert_eq!((f.width, f.height), (1920, 1080));
|
||||
assert_eq!(f.seq, 7);
|
||||
assert!(f.keyframe);
|
||||
// v5:光标字段必须透传,丢它=标记永远不显示,且丢法是静默的
|
||||
assert_eq!(f.cursor, Some((640, 480)));
|
||||
assert_eq!(f.payload, payload);
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user