webp_screenshot_rust/pipeline/
streaming.rs1use crate::{
10 capture::ScreenCapture,
11 encoder::{WebPEncoder, simd::SimdConverter},
12 error::{CaptureError, CaptureResult},
13 memory_pool::MemoryPool,
14 pipeline::zero_copy::ZeroCopyOptimizer,
15 types::{RawImage, WebPConfig},
16};
17
18use crossbeam_channel::{bounded, Receiver, Sender};
19use parking_lot::Mutex;
20use std::{
21 sync::{
22 atomic::{AtomicBool, AtomicU64, Ordering},
23 Arc,
24 },
25 thread,
26 time::{Duration, Instant},
27};
28
29#[derive(Clone)]
31struct Frame {
32 #[allow(dead_code)]
33 id: u64,
34 image: RawImage,
35 #[allow(dead_code)]
36 timestamp: Instant,
37 capture_duration: Duration,
38}
39
40#[derive(Debug, Clone)]
42pub struct StreamingConfig {
43 pub target_fps: u32,
45 pub buffer_size: usize,
47 pub capture_threads: usize,
49 pub encoding_threads: usize,
51 pub adaptive_quality: bool,
53 pub allow_frame_drop: bool,
55 pub webp_config: WebPConfig,
57 pub use_zero_copy: bool,
59 pub use_gpu: bool,
61}
62
63impl Default for StreamingConfig {
64 fn default() -> Self {
65 let cpu_count = num_cpus::get();
66
67 Self {
68 target_fps: 30,
69 buffer_size: 60, capture_threads: 1,
71 encoding_threads: (cpu_count / 2).max(2),
72 adaptive_quality: true,
73 allow_frame_drop: true,
74 webp_config: WebPConfig::fast(),
75 use_zero_copy: true,
76 use_gpu: false,
77 }
78 }
79}
80
81#[derive(Debug, Clone, Default)]
83pub struct StreamingStats {
84 pub frames_captured: u64,
85 pub frames_encoded: u64,
86 pub frames_dropped: u64,
87 pub bytes_encoded: u64,
88 pub total_capture_time: Duration,
89 pub total_encode_time: Duration,
90 pub current_fps: f32,
91 pub current_bitrate: u64,
92 pub avg_capture_time: Duration,
93 pub avg_encode_time: Duration,
94}
95
96pub struct StreamingPipeline {
98 config: StreamingConfig,
99 capturer: Arc<Box<dyn ScreenCapture>>,
100 running: Arc<AtomicBool>,
101 stats: Arc<Mutex<StreamingStats>>,
102 frame_counter: Arc<AtomicU64>,
103 memory_pool: Arc<MemoryPool>,
104 zero_copy: Arc<ZeroCopyOptimizer>,
105 #[allow(dead_code)]
106 simd_converter: Arc<SimdConverter>,
107}
108
109impl StreamingPipeline {
110 pub fn new(
112 capturer: Box<dyn ScreenCapture>,
113 config: StreamingConfig,
114 ) -> Self {
115 Self {
116 config,
117 capturer: Arc::new(capturer),
118 running: Arc::new(AtomicBool::new(false)),
119 stats: Arc::new(Mutex::new(StreamingStats::default())),
120 frame_counter: Arc::new(AtomicU64::new(0)),
121 memory_pool: MemoryPool::new(),
122 zero_copy: Arc::new(ZeroCopyOptimizer::new()),
123 simd_converter: Arc::new(SimdConverter::new()),
124 }
125 }
126
127 pub fn start<F>(&self, callback: F) -> CaptureResult<()>
129 where
130 F: FnMut(Vec<u8>) + Send + 'static,
131 {
132 if self.running.load(Ordering::Relaxed) {
133 return Err(CaptureError::CaptureFailed(
134 "Pipeline already running".to_string(),
135 ));
136 }
137
138 self.running.store(true, Ordering::Relaxed);
139
140 let (capture_tx, capture_rx) = bounded::<Frame>(self.config.buffer_size);
142 let (encode_tx, encode_rx) = bounded::<Vec<u8>>(self.config.buffer_size);
143
144 self.start_capture_threads(capture_tx);
146
147 self.start_encoding_threads(capture_rx, encode_tx);
149
150 self.start_output_thread(encode_rx, callback);
152
153 self.start_stats_thread();
155
156 Ok(())
157 }
158
159 pub fn stop(&self) {
161 self.running.store(false, Ordering::Relaxed);
162 }
163
164 pub fn is_running(&self) -> bool {
166 self.running.load(Ordering::Relaxed)
167 }
168
169 pub fn stats(&self) -> StreamingStats {
171 self.stats.lock().clone()
172 }
173
174 fn start_capture_threads(&self, tx: Sender<Frame>) {
176 for _thread_id in 0..self.config.capture_threads {
177 let capturer = Arc::clone(&self.capturer);
178 let running = Arc::clone(&self.running);
179 let frame_counter = Arc::clone(&self.frame_counter);
180 let _memory_pool = Arc::clone(&self.memory_pool);
181 let zero_copy = Arc::clone(&self.zero_copy);
182 let tx = tx.clone();
183 let target_fps = self.config.target_fps;
184 let use_zero_copy = self.config.use_zero_copy;
185
186 thread::spawn(move || {
187 let frame_duration = Duration::from_micros(1_000_000 / target_fps as u64);
188 let mut next_frame_time = Instant::now();
189
190 while running.load(Ordering::Relaxed) {
191 let capture_start = Instant::now();
192
193 let image = if use_zero_copy {
195 zero_copy.capture_zero_copy(&**capturer, 0)
196 } else {
197 capturer.capture_display(0)
198 };
199
200 if let Ok(image) = image {
201 let capture_duration = capture_start.elapsed();
202 let frame_id = frame_counter.fetch_add(1, Ordering::Relaxed);
203
204 let frame = Frame {
205 id: frame_id,
206 image,
207 timestamp: Instant::now(),
208 capture_duration,
209 };
210
211 if tx.send(frame).is_err() {
213 break;
215 }
216 }
217
218 next_frame_time += frame_duration;
220 let now = Instant::now();
221 if next_frame_time > now {
222 thread::sleep(next_frame_time - now);
223 }
224 }
225 });
226 }
227 }
228
229 fn start_encoding_threads(&self, rx: Receiver<Frame>, tx: Sender<Vec<u8>>) {
231 for _thread_id in 0..self.config.encoding_threads {
232 let rx = rx.clone();
233 let tx = tx.clone();
234 let running = Arc::clone(&self.running);
235 let stats = Arc::clone(&self.stats);
236 let webp_config = self.config.webp_config.clone();
237 let adaptive_quality = self.config.adaptive_quality;
238 let allow_frame_drop = self.config.allow_frame_drop;
239
240 thread::spawn(move || {
241 let mut encoder = WebPEncoder::new();
242 let mut current_config = webp_config;
243
244 while running.load(Ordering::Relaxed) {
245 let frame = match rx.recv_timeout(Duration::from_millis(100)) {
247 Ok(frame) => frame,
248 Err(_) => continue,
249 };
250
251 let encode_start = Instant::now();
252
253 if allow_frame_drop && rx.len() > 10 {
255 let mut stats = stats.lock();
257 stats.frames_dropped += 1;
258 continue;
259 }
260
261 if adaptive_quality {
263 current_config = Self::adjust_quality(
264 current_config,
265 frame.capture_duration,
266 rx.len(),
267 );
268 }
269
270 match encoder.encode(&frame.image, ¤t_config) {
272 Ok(webp_data) => {
273 let encode_duration = encode_start.elapsed();
274
275 {
277 let mut stats = stats.lock();
278 stats.frames_encoded += 1;
279 stats.bytes_encoded += webp_data.len() as u64;
280 stats.total_encode_time += encode_duration;
281 }
282
283 if tx.send(webp_data).is_err() {
285 break;
286 }
287 }
288 Err(e) => {
289 eprintln!("Encoding error: {}", e);
290 }
291 }
292 }
293 });
294 }
295 }
296
297 fn start_output_thread<F>(&self, rx: Receiver<Vec<u8>>, mut callback: F)
299 where
300 F: FnMut(Vec<u8>) + Send + 'static,
301 {
302 let running = Arc::clone(&self.running);
303
304 thread::spawn(move || {
305 while running.load(Ordering::Relaxed) {
306 match rx.recv_timeout(Duration::from_millis(100)) {
307 Ok(data) => callback(data),
308 Err(_) => continue,
309 }
310 }
311 });
312 }
313
314 fn start_stats_thread(&self) {
316 let running = Arc::clone(&self.running);
317 let stats = Arc::clone(&self.stats);
318 let frame_counter = Arc::clone(&self.frame_counter);
319
320 thread::spawn(move || {
321 let mut last_frame_count = 0u64;
322 let mut last_bytes = 0u64;
323 let mut last_time = Instant::now();
324
325 while running.load(Ordering::Relaxed) {
326 thread::sleep(Duration::from_secs(1));
327
328 let current_frames = frame_counter.load(Ordering::Relaxed);
329 let elapsed = last_time.elapsed();
330
331 let mut stats = stats.lock();
332
333 let frames_delta = current_frames - last_frame_count;
335 stats.current_fps = frames_delta as f32 / elapsed.as_secs_f32();
336
337 let bytes_delta = stats.bytes_encoded - last_bytes;
339 stats.current_bitrate = (bytes_delta * 8) / elapsed.as_secs().max(1);
340
341 if stats.frames_captured > 0 {
343 stats.avg_capture_time =
344 stats.total_capture_time / stats.frames_captured as u32;
345 }
346 if stats.frames_encoded > 0 {
347 stats.avg_encode_time =
348 stats.total_encode_time / stats.frames_encoded as u32;
349 }
350
351 stats.frames_captured = current_frames;
352
353 last_frame_count = current_frames;
354 last_bytes = stats.bytes_encoded;
355 last_time = Instant::now();
356 }
357 });
358 }
359
360 fn adjust_quality(
362 mut config: WebPConfig,
363 capture_duration: Duration,
364 buffer_depth: usize,
365 ) -> WebPConfig {
366 if capture_duration > Duration::from_millis(20) || buffer_depth > 30 {
368 config.quality = (config.quality - 5).max(60);
369 config.method = (config.method - 1).max(0);
370 } else if capture_duration < Duration::from_millis(10) && buffer_depth < 10 {
371 config.quality = (config.quality + 2).min(90);
373 config.method = (config.method + 1).min(4);
374 }
375
376 config
377 }
378}
379
380pub struct StreamingPipelineBuilder {
382 config: StreamingConfig,
383}
384
385impl StreamingPipelineBuilder {
386 pub fn new() -> Self {
388 Self {
389 config: StreamingConfig::default(),
390 }
391 }
392
393 pub fn target_fps(mut self, fps: u32) -> Self {
395 self.config.target_fps = fps;
396 self
397 }
398
399 pub fn buffer_size(mut self, size: usize) -> Self {
401 self.config.buffer_size = size;
402 self
403 }
404
405 pub fn capture_threads(mut self, count: usize) -> Self {
407 self.config.capture_threads = count;
408 self
409 }
410
411 pub fn encoding_threads(mut self, count: usize) -> Self {
413 self.config.encoding_threads = count;
414 self
415 }
416
417 pub fn adaptive_quality(mut self, enabled: bool) -> Self {
419 self.config.adaptive_quality = enabled;
420 self
421 }
422
423 pub fn allow_frame_drop(mut self, enabled: bool) -> Self {
425 self.config.allow_frame_drop = enabled;
426 self
427 }
428
429 pub fn webp_config(mut self, config: WebPConfig) -> Self {
431 self.config.webp_config = config;
432 self
433 }
434
435 pub fn use_zero_copy(mut self, enabled: bool) -> Self {
437 self.config.use_zero_copy = enabled;
438 self
439 }
440
441 pub fn use_gpu(mut self, enabled: bool) -> Self {
443 self.config.use_gpu = enabled;
444 self
445 }
446
447 pub fn build(self, capturer: Box<dyn ScreenCapture>) -> StreamingPipeline {
449 StreamingPipeline::new(capturer, self.config)
450 }
451}
452
453impl Default for StreamingPipelineBuilder {
454 fn default() -> Self {
455 Self::new()
456 }
457}
458
459#[cfg(test)]
460mod tests {
461 use super::*;
462
463 #[test]
464 fn test_pipeline_builder() {
465 let config = StreamingPipelineBuilder::new()
466 .target_fps(60)
467 .buffer_size(120)
468 .capture_threads(2)
469 .encoding_threads(4)
470 .adaptive_quality(true)
471 .build(crate::capture::Capturer::new().unwrap());
472
473 assert_eq!(config.config.target_fps, 60);
474 assert_eq!(config.config.buffer_size, 120);
475 }
476
477 #[test]
478 fn test_quality_adjustment() {
479 let config = WebPConfig {
480 quality: 80,
481 method: 4,
482 ..Default::default()
483 };
484
485 let adjusted = StreamingPipeline::adjust_quality(
487 config.clone(),
488 Duration::from_millis(25),
489 40,
490 );
491 assert!(adjusted.quality < config.quality);
492
493 let adjusted = StreamingPipeline::adjust_quality(
495 config.clone(),
496 Duration::from_millis(5),
497 5,
498 );
499 assert!(adjusted.quality > config.quality);
500 }
501}