Skip to main content

webp_screenshot_rust/pipeline/
streaming.rs

1//! Ultra-high performance streaming pipeline for real-time capture and encoding
2//!
3//! Features:
4//! - Multi-threaded capture and encoding
5//! - Ring buffer for frame management
6//! - Adaptive quality based on performance
7//! - Frame dropping for consistent FPS
8
9use 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/// Frame data in the pipeline
30#[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/// Streaming pipeline configuration
41#[derive(Debug, Clone)]
42pub struct StreamingConfig {
43    /// Target frames per second
44    pub target_fps: u32,
45    /// Maximum frames in buffer
46    pub buffer_size: usize,
47    /// Number of capture threads
48    pub capture_threads: usize,
49    /// Number of encoding threads
50    pub encoding_threads: usize,
51    /// Enable adaptive quality
52    pub adaptive_quality: bool,
53    /// Enable frame dropping
54    pub allow_frame_drop: bool,
55    /// Initial WebP configuration
56    pub webp_config: WebPConfig,
57    /// Use zero-copy optimizations
58    pub use_zero_copy: bool,
59    /// Use GPU encoding if available
60    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, // 2 seconds at 30fps
70            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/// Streaming statistics
82#[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
96/// Ultra streaming pipeline for high-performance capture
97pub 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    /// Create a new streaming pipeline
111    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    /// Start the streaming pipeline
128    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        // Create channels for frame passing
141        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        // Start capture thread(s)
145        self.start_capture_threads(capture_tx);
146
147        // Start encoding threads
148        self.start_encoding_threads(capture_rx, encode_tx);
149
150        // Start output thread
151        self.start_output_thread(encode_rx, callback);
152
153        // Start statistics thread
154        self.start_stats_thread();
155
156        Ok(())
157    }
158
159    /// Stop the streaming pipeline
160    pub fn stop(&self) {
161        self.running.store(false, Ordering::Relaxed);
162    }
163
164    /// Check if pipeline is running
165    pub fn is_running(&self) -> bool {
166        self.running.load(Ordering::Relaxed)
167    }
168
169    /// Get current statistics
170    pub fn stats(&self) -> StreamingStats {
171        self.stats.lock().clone()
172    }
173
174    /// Start capture threads
175    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                    // Capture frame
194                    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                        // Send frame to encoding pipeline
212                        if tx.send(frame).is_err() {
213                            // Channel full or closed
214                            break;
215                        }
216                    }
217
218                    // Maintain target FPS
219                    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    /// Start encoding threads
230    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                    // Receive frame
246                    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                    // Check if frame should be dropped
254                    if allow_frame_drop && rx.len() > 10 {
255                        // Skip encoding if buffer is backing up
256                        let mut stats = stats.lock();
257                        stats.frames_dropped += 1;
258                        continue;
259                    }
260
261                    // Adaptive quality adjustment
262                    if adaptive_quality {
263                        current_config = Self::adjust_quality(
264                            current_config,
265                            frame.capture_duration,
266                            rx.len(),
267                        );
268                    }
269
270                    // Encode frame
271                    match encoder.encode(&frame.image, &current_config) {
272                        Ok(webp_data) => {
273                            let encode_duration = encode_start.elapsed();
274
275                            // Update stats
276                            {
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                            // Send encoded frame
284                            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    /// Start output thread
298    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    /// Start statistics thread
315    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                // Calculate FPS
334                let frames_delta = current_frames - last_frame_count;
335                stats.current_fps = frames_delta as f32 / elapsed.as_secs_f32();
336
337                // Calculate bitrate
338                let bytes_delta = stats.bytes_encoded - last_bytes;
339                stats.current_bitrate = (bytes_delta * 8) / elapsed.as_secs().max(1);
340
341                // Calculate averages
342                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    /// Adjust quality based on performance
361    fn adjust_quality(
362        mut config: WebPConfig,
363        capture_duration: Duration,
364        buffer_depth: usize,
365    ) -> WebPConfig {
366        // If capture is slow or buffer is filling, reduce quality
367        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            // If performance is good, increase quality
372            config.quality = (config.quality + 2).min(90);
373            config.method = (config.method + 1).min(4);
374        }
375
376        config
377    }
378}
379
380/// Builder for streaming pipeline
381pub struct StreamingPipelineBuilder {
382    config: StreamingConfig,
383}
384
385impl StreamingPipelineBuilder {
386    /// Create a new builder
387    pub fn new() -> Self {
388        Self {
389            config: StreamingConfig::default(),
390        }
391    }
392
393    /// Set target FPS
394    pub fn target_fps(mut self, fps: u32) -> Self {
395        self.config.target_fps = fps;
396        self
397    }
398
399    /// Set buffer size
400    pub fn buffer_size(mut self, size: usize) -> Self {
401        self.config.buffer_size = size;
402        self
403    }
404
405    /// Set number of capture threads
406    pub fn capture_threads(mut self, count: usize) -> Self {
407        self.config.capture_threads = count;
408        self
409    }
410
411    /// Set number of encoding threads
412    pub fn encoding_threads(mut self, count: usize) -> Self {
413        self.config.encoding_threads = count;
414        self
415    }
416
417    /// Enable adaptive quality
418    pub fn adaptive_quality(mut self, enabled: bool) -> Self {
419        self.config.adaptive_quality = enabled;
420        self
421    }
422
423    /// Enable frame dropping
424    pub fn allow_frame_drop(mut self, enabled: bool) -> Self {
425        self.config.allow_frame_drop = enabled;
426        self
427    }
428
429    /// Set WebP configuration
430    pub fn webp_config(mut self, config: WebPConfig) -> Self {
431        self.config.webp_config = config;
432        self
433    }
434
435    /// Enable zero-copy
436    pub fn use_zero_copy(mut self, enabled: bool) -> Self {
437        self.config.use_zero_copy = enabled;
438        self
439    }
440
441    /// Enable GPU encoding
442    pub fn use_gpu(mut self, enabled: bool) -> Self {
443        self.config.use_gpu = enabled;
444        self
445    }
446
447    /// Build the pipeline
448    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        // Test quality reduction
486        let adjusted = StreamingPipeline::adjust_quality(
487            config.clone(),
488            Duration::from_millis(25),
489            40,
490        );
491        assert!(adjusted.quality < config.quality);
492
493        // Test quality increase
494        let adjusted = StreamingPipeline::adjust_quality(
495            config.clone(),
496            Duration::from_millis(5),
497            5,
498        );
499        assert!(adjusted.quality > config.quality);
500    }
501}