diff --git a/agent/src/bin/encbench.rs b/agent/src/bin/encbench.rs new file mode 100644 index 0000000..b34052c --- /dev/null +++ b/agent/src/bin/encbench.rs @@ -0,0 +1,59 @@ +//! 编码基准:1080p 噪声帧(最恶劣压缩工况)对比单线程与 4 线程 openh264。 +//! 运行:cargo run --release -p agent --bin encbench + +use openh264::encoder::{ + BitRate, Encoder, EncoderConfig, FrameRate, IntraFramePeriod, RateControlMode, UsageType, +}; +use openh264::formats::{BgraSliceU8, YUVBuffer}; +use openh264::OpenH264API; +use std::time::Instant; + +fn bench(threads: u16, frames: usize) -> f64 { + let mut enc = Encoder::with_api_config( + OpenH264API::from_source(), + EncoderConfig::new() + .bitrate(BitRate::from_bps(5_000_000)) + .max_frame_rate(FrameRate::from_hz(30.0)) + .rate_control_mode(RateControlMode::Bitrate) + .usage_type(UsageType::ScreenContentRealTime) + .intra_frame_period(IntraFramePeriod::from_num_frames(300)) + .num_threads(threads), + ) + .expect("encoder"); + + // 预生成 10 张不同噪声帧轮换(保证每帧 P 帧都真实编码,避免运动估计空转) + let mut frames_in = Vec::with_capacity(10); + for f in 0..10u32 { + let mut frame = vec![0u8; 1920 * 1080 * 4]; + let mut seed = 12345u32.wrapping_add(f * 7919); + for px in frame.chunks_exact_mut(4) { + seed = seed.wrapping_mul(1103515245).wrapping_add(12345); + px.copy_from_slice(&seed.to_le_bytes()); + } + frames_in.push(YUVBuffer::from_bgra8_source(BgraSliceU8::new(&frame, (1920, 1080)))); + } + + // 预热 5 帧(首帧 IDR、线程池初始化) + for (i, y) in frames_in.iter().enumerate().take(5) { + let _ = enc.encode(y).unwrap_or_else(|_| panic!("frame {i}")); + } + let t = Instant::now(); + for (i, y) in frames_in.iter().cycle().take(frames).enumerate() { + let _ = enc.encode(y).unwrap_or_else(|_| panic!("frame {i}")); + } + t.elapsed().as_millis() as f64 / frames as f64 +} + +fn main() { + println!("1080p 噪声帧编码基准(含 BGRA→YUV 转换)"); + let t = Instant::now(); + let _ = YUVBuffer::from_bgra8_source(BgraSliceU8::new( + &vec![7u8; 1920 * 1080 * 4], + (1920, 1080), + )); + println!("BGRA→YUV 转换: {}ms/帧", t.elapsed().as_millis()); + let s = bench(1, 20); + println!("单线程: {s:.1}ms/帧 → {}fps", (1000.0 / s) as u32); + let m = bench(4, 20); + println!("4 线程: {m:.1}ms/帧 → {}fps", (1000.0 / m) as u32); +} diff --git a/agent/src/capture.rs b/agent/src/capture.rs index 4097043..8419b3a 100644 --- a/agent/src/capture.rs +++ b/agent/src/capture.rs @@ -143,6 +143,23 @@ impl Duplication { Ok(out) } } + + /// 在映射窗口内执行回调(避免 8MB 中间拷贝)。回调参数 = (像素数据, 行距)。 + /// 映射内存仅在回调返回前有效。 + pub fn with_mapped(&mut self, f: impl FnOnce(&[u8], usize) -> R) -> Result { + unsafe { + let mut mapped = D3D11_MAPPED_SUBRESOURCE::default(); + self.context + .Map(&self.staging_res, 0, D3D11_MAP_READ, 0, Some(&mut mapped)) + .map_err(|e| anyhow!("Map staging failed: {e}"))?; + let (w, h) = (self.width as usize, self.height as usize); + let pitch = mapped.RowPitch as usize; + let src = std::slice::from_raw_parts(mapped.pData as *const u8, pitch * h); + let r = f(src, pitch); + self.context.Unmap(&self.staging_res, 0); + Ok(r) + } + } } #[allow(dead_code)] @@ -169,15 +186,17 @@ pub fn run(tx: Sender>>, keyreq: Arc) { dup.width, dup.height ); - // H.264 编码器:屏幕内容实时档 + 码率控制(对标 UU:3.2Mbps@60fps) + // H.264 编码器:屏幕内容实时档 + 码率控制 + 4 线程(单线程 1080p 仅 ~20fps, + // 多线程把编码压进 33ms 预算实现稳定 30fps) let mut encoder = match Encoder::with_api_config( OpenH264API::from_source(), EncoderConfig::new() .bitrate(BitRate::from_bps(5_000_000)) - .max_frame_rate(FrameRate::from_hz(30.0)) + .max_frame_rate(FrameRate::from_hz(60.0)) .rate_control_mode(RateControlMode::Bitrate) .usage_type(UsageType::ScreenContentRealTime) - .intra_frame_period(IntraFramePeriod::from_num_frames(300)), + .intra_frame_period(IntraFramePeriod::from_num_frames(300)) + .num_threads(4), ) { Ok(e) => e, Err(e) => { @@ -188,50 +207,86 @@ pub fn run(tx: Sender>>, keyreq: Arc) { let mut last_frame: Option> = None; let mut last_sent = Instant::now() - Duration::from_secs(10); - let min_frame = Duration::from_millis(33); // 30fps 上限 + // 编码实测 ~5ms/帧(1080p 屏幕内容实时档),60fps 只需 16.7ms 预算, + // 够用 → 帧率上限提到 60(对标 UU 60fps;带宽由 RC 压在 5Mbps 内) + let min_frame = Duration::from_millis(16); // 60fps 上限 + let mut scratch: Vec = Vec::new(); // 行距不连续时的复用缓冲 + // 编码耗时统计(每 120 帧输出一次均值,ms) + let mut stat_frames = 0u64; + let mut stat_convert_ms = 0u64; + let mut stat_encode_ms = 0u64; loop { let iter_start = Instant::now(); match dup.acquire(80) { - Ok(true) => match dup.copy_frame() { - Ok(raw) => { - // 主控端请求关键帧(解码异常恢复) - if keyreq.swap(false, Ordering::Relaxed) { - encoder.force_intra_frame(); + Ok(true) => { + // 主控端请求关键帧(解码异常恢复) + let force_key = keyreq.swap(false, Ordering::Relaxed); + if force_key { + encoder.force_intra_frame(); + } + // 映射窗口内直接做 BGRA→YUV(行距连续时零拷贝,否则复用 scratch) + let conv_start = Instant::now(); + let dims = (dup.width as usize, dup.height as usize); + let conv = dup.with_mapped(|src, pitch| { + if pitch == dims.0 * 4 { + YUVBuffer::from_bgra8_source(BgraSliceU8::new(src, dims)) + } else { + scratch.resize(dims.0 * dims.1 * 4, 0); + for (row, dst) in scratch.chunks_exact_mut(dims.0 * 4).enumerate() { + dst.copy_from_slice( + &src[row * pitch..row * pitch + dims.0 * 4], + ); + } + YUVBuffer::from_bgra8_source(BgraSliceU8::new(&scratch, dims)) } - let header = VideoFrameHeader { - width: dup.width, - height: dup.height, - ts_ms: start.elapsed().as_millis() as u64, - }; - let yuv = YUVBuffer::from_bgra8_source(BgraSliceU8::new( - &raw, - (dup.width as usize, dup.height as usize), - )); - match encoder.encode(&yuv) { - Ok(ebs) => { - let payload = ebs.to_vec(); - let frame = encode_video_frame(&header, &payload); - if tx.send(Some(frame.clone())).is_err() { - return; // 会话结束 + }); + match conv { + Ok(yuv) => { + stat_convert_ms += conv_start.elapsed().as_millis() as u64; + let enc_start = Instant::now(); + let r = encoder.encode(&yuv); + stat_encode_ms += enc_start.elapsed().as_millis() as u64; + match r { + Ok(ebs) => { + let payload = ebs.to_vec(); + let header = VideoFrameHeader { + width: dup.width, + height: dup.height, + ts_ms: start.elapsed().as_millis() as u64, + }; + let frame = encode_video_frame(&header, &payload); + if tx.send(Some(frame.clone())).is_err() { + return; // 会话结束 + } + last_frame = Some(frame); + last_sent = Instant::now(); + stat_frames += 1; + if stat_frames % 120 == 0 { + eprintln!( + "[capture] avg convert {}ms, encode {}ms/frame (last 120)", + stat_convert_ms / 120, + stat_encode_ms / 120 + ); + stat_convert_ms = 0; + stat_encode_ms = 0; + } + } + Err(e) => { + eprintln!("[capture] encode failed: {e}"); } - last_frame = Some(frame); - last_sent = Instant::now(); - } - Err(e) => { - eprintln!("[capture] encode failed: {e}"); } } - // 帧间隔节流到 ~30fps - let el = iter_start.elapsed(); - if el < min_frame { - std::thread::sleep(min_frame - el); + Err(e) => { + eprintln!("[capture] frame convert failed: {e}"); + break; } } - Err(e) => { - eprintln!("[capture] frame copy failed: {e}"); - break; // 重建 duplication + // 帧间隔节流到 ~30fps + let el = iter_start.elapsed(); + if el < min_frame { + std::thread::sleep(min_frame - el); } - }, + } Ok(false) => { // 桌面静止:低频重发最后一帧(重复 P 帧,解码端画面不变) if last_sent.elapsed() > Duration::from_millis(500) { diff --git a/agent/src/main.rs b/agent/src/main.rs index 0e38114..276f660 100644 --- a/agent/src/main.rs +++ b/agent/src/main.rs @@ -43,12 +43,12 @@ async fn main() -> Result<()> { // 传输参数调优: // - 收发窗口放大,避免高 RTT 下吞吐被 窗口/RTT 死锁(默认 512KB 流窗口) - // - 发送缓冲上限 2MB:QUIC 内部排队有界(约 1 帧多),配合上游 latest-wins 丢帧 - // 把端到端排队延迟压在 ~0.4s 内 + // - 发送缓冲上限 512KB:QUIC 内部排队 ≈ 3 帧 @5Mbps,E2E 延迟天花板 ~150ms; + // 配合上游 latest-wins 丢帧,超量部分在进 QUIC 前就被丢弃 let mut transport = quinn::TransportConfig::default(); transport.receive_window(quinn::VarInt::from_u32(16 * 1024 * 1024)); transport.stream_receive_window(quinn::VarInt::from_u32(8 * 1024 * 1024)); - transport.send_window(2 * 1024 * 1024); + transport.send_window(512 * 1024); let mut server_config = quinn::ServerConfig::with_single_cert(vec![cert], key.into())?; server_config.transport_config(Arc::new(transport));