Skip to main content

oximedia_codec/multipass/
vbv.rs

1//! VBV (Video Buffering Verifier) buffer model implementation.
2//!
3//! The VBV model ensures that encoded video can be decoded without buffer
4//! underflow or overflow, which is critical for streaming applications.
5
6#![forbid(unsafe_code)]
7#![allow(clippy::cast_precision_loss)]
8#![allow(clippy::cast_possible_truncation)]
9#![allow(clippy::cast_sign_loss)]
10#![allow(clippy::cast_lossless)]
11
12use crate::frame::FrameType;
13
14/// VBV buffer configuration.
15#[derive(Clone, Debug)]
16pub struct VbvConfig {
17    /// Buffer size in bits.
18    pub buffer_size: u64,
19    /// Maximum bitrate in bits per second.
20    pub max_bitrate: u64,
21    /// Initial buffer fullness (0.0-1.0).
22    pub initial_fullness: f64,
23    /// Frame rate numerator.
24    pub framerate_num: u32,
25    /// Frame rate denominator.
26    pub framerate_den: u32,
27}
28
29impl VbvConfig {
30    /// Create a new VBV configuration.
31    #[must_use]
32    pub fn new(buffer_size: u64, max_bitrate: u64, framerate_num: u32, framerate_den: u32) -> Self {
33        Self {
34            buffer_size,
35            max_bitrate,
36            initial_fullness: 0.75,
37            framerate_num,
38            framerate_den,
39        }
40    }
41
42    /// Set initial buffer fullness.
43    #[must_use]
44    pub fn with_initial_fullness(mut self, fullness: f64) -> Self {
45        self.initial_fullness = fullness.clamp(0.0, 1.0);
46        self
47    }
48
49    /// Calculate frame period in seconds.
50    #[must_use]
51    pub fn frame_period(&self) -> f64 {
52        self.framerate_den as f64 / self.framerate_num as f64
53    }
54
55    /// Calculate bits added per frame period.
56    #[must_use]
57    pub fn bits_per_frame(&self) -> f64 {
58        self.max_bitrate as f64 * self.frame_period()
59    }
60}
61
62/// VBV buffer state and management.
63pub struct VbvBuffer {
64    config: VbvConfig,
65    /// Current buffer fullness in bits.
66    current_fullness: f64,
67    /// Minimum observed fullness (for debugging).
68    min_fullness: f64,
69    /// Maximum observed fullness (for debugging).
70    max_fullness: f64,
71    /// Number of frames processed.
72    frame_count: u64,
73    /// Total underflows detected.
74    underflow_count: u64,
75    /// Total overflows detected.
76    overflow_count: u64,
77}
78
79impl VbvBuffer {
80    /// Create a new VBV buffer.
81    #[must_use]
82    pub fn new(config: VbvConfig) -> Self {
83        let current_fullness = config.buffer_size as f64 * config.initial_fullness;
84
85        Self {
86            config,
87            current_fullness,
88            min_fullness: current_fullness,
89            max_fullness: current_fullness,
90            frame_count: 0,
91            underflow_count: 0,
92            overflow_count: 0,
93        }
94    }
95
96    /// Check if we can send a frame of given size without overflow.
97    #[must_use]
98    pub fn can_send_frame(&self, frame_bits: u64) -> bool {
99        let new_fullness = self.current_fullness - frame_bits as f64;
100        new_fullness >= 0.0
101    }
102
103    /// Get maximum frame size that can be sent without underflow.
104    #[must_use]
105    pub fn max_frame_size(&self) -> u64 {
106        self.current_fullness as u64
107    }
108
109    /// Get minimum frame size to avoid overflow after refill.
110    #[must_use]
111    pub fn min_frame_size(&self) -> u64 {
112        let bits_added = self.config.bits_per_frame();
113        let max_fullness = self.config.buffer_size as f64;
114        let available_after_refill = self.current_fullness + bits_added;
115
116        if available_after_refill > max_fullness {
117            (available_after_refill - max_fullness) as u64
118        } else {
119            0
120        }
121    }
122
123    /// Update buffer state after encoding a frame.
124    pub fn update(&mut self, frame_bits: u64) -> VbvUpdateResult {
125        // Remove bits for encoded frame
126        self.current_fullness -= frame_bits as f64;
127
128        let mut result = VbvUpdateResult {
129            underflow: false,
130            overflow: false,
131            fullness_ratio: 0.0,
132            available_bits: 0,
133        };
134
135        // Check for underflow
136        if self.current_fullness < 0.0 {
137            result.underflow = true;
138            self.underflow_count += 1;
139            self.current_fullness = 0.0;
140        }
141
142        // Add bits for next frame period
143        let bits_added = self.config.bits_per_frame();
144        self.current_fullness += bits_added;
145
146        // Check for overflow
147        if self.current_fullness > self.config.buffer_size as f64 {
148            result.overflow = true;
149            self.overflow_count += 1;
150            self.current_fullness = self.config.buffer_size as f64;
151        }
152
153        // Update min/max tracking
154        self.min_fullness = self.min_fullness.min(self.current_fullness);
155        self.max_fullness = self.max_fullness.max(self.current_fullness);
156
157        result.fullness_ratio = self.current_fullness / self.config.buffer_size as f64;
158        result.available_bits = self.current_fullness as u64;
159
160        self.frame_count += 1;
161        result
162    }
163
164    /// Calculate target frame size based on buffer state.
165    #[must_use]
166    pub fn target_frame_size(&self, frame_type: FrameType, base_size: f64) -> u64 {
167        let fullness_ratio = self.current_fullness / self.config.buffer_size as f64;
168
169        // Adjust based on buffer fullness
170        // If buffer is full, we can use more bits
171        // If buffer is empty, we need to use fewer bits
172        let buffer_factor = if fullness_ratio > 0.75 {
173            1.2 // Buffer is full, can be generous
174        } else if fullness_ratio > 0.5 {
175            1.0 // Normal operation
176        } else if fullness_ratio > 0.25 {
177            0.8 // Buffer getting low, be conservative
178        } else {
179            0.6 // Buffer critically low, minimize size
180        };
181
182        // Adjust for frame type
183        let type_factor = match frame_type {
184            FrameType::Key => 3.0,    // Keyframes need more bits
185            FrameType::Inter => 1.0,  // Inter frames are baseline
186            FrameType::BiDir => 0.5,  // B-frames use fewer bits
187            FrameType::Switch => 2.0, // Switch frames need extra bits
188        };
189
190        let target = base_size * buffer_factor * type_factor;
191
192        // Ensure we don't exceed buffer capacity
193        target.min(self.max_frame_size() as f64) as u64
194    }
195
196    /// Get current buffer fullness ratio (0.0-1.0).
197    #[must_use]
198    pub fn fullness_ratio(&self) -> f64 {
199        self.current_fullness / self.config.buffer_size as f64
200    }
201
202    /// Get current buffer fullness in bits.
203    #[must_use]
204    pub fn current_fullness(&self) -> u64 {
205        self.current_fullness as u64
206    }
207
208    /// Get VBV statistics.
209    #[must_use]
210    pub fn statistics(&self) -> VbvStatistics {
211        VbvStatistics {
212            frame_count: self.frame_count,
213            underflow_count: self.underflow_count,
214            overflow_count: self.overflow_count,
215            current_fullness: self.current_fullness,
216            min_fullness: self.min_fullness,
217            max_fullness: self.max_fullness,
218            buffer_size: self.config.buffer_size,
219        }
220    }
221
222    /// Reset buffer state.
223    pub fn reset(&mut self) {
224        self.current_fullness = self.config.buffer_size as f64 * self.config.initial_fullness;
225        self.min_fullness = self.current_fullness;
226        self.max_fullness = self.current_fullness;
227        self.frame_count = 0;
228        self.underflow_count = 0;
229        self.overflow_count = 0;
230    }
231}
232
233/// Result of a VBV buffer update.
234#[derive(Clone, Debug)]
235pub struct VbvUpdateResult {
236    /// Buffer underflow occurred.
237    pub underflow: bool,
238    /// Buffer overflow occurred.
239    pub overflow: bool,
240    /// Current buffer fullness ratio (0.0-1.0).
241    pub fullness_ratio: f64,
242    /// Available bits in buffer.
243    pub available_bits: u64,
244}
245
246/// VBV buffer statistics.
247#[derive(Clone, Debug)]
248pub struct VbvStatistics {
249    /// Total frames processed.
250    pub frame_count: u64,
251    /// Number of underflows.
252    pub underflow_count: u64,
253    /// Number of overflows.
254    pub overflow_count: u64,
255    /// Current buffer fullness in bits.
256    pub current_fullness: f64,
257    /// Minimum observed fullness.
258    pub min_fullness: f64,
259    /// Maximum observed fullness.
260    pub max_fullness: f64,
261    /// Total buffer size.
262    pub buffer_size: u64,
263}
264
265impl VbvStatistics {
266    /// Check if VBV compliance is good (no underflows/overflows).
267    #[must_use]
268    pub fn is_compliant(&self) -> bool {
269        self.underflow_count == 0 && self.overflow_count == 0
270    }
271
272    /// Get buffer utilization statistics.
273    #[must_use]
274    pub fn utilization(&self) -> BufferUtilization {
275        BufferUtilization {
276            min_ratio: self.min_fullness / self.buffer_size as f64,
277            max_ratio: self.max_fullness / self.buffer_size as f64,
278            current_ratio: self.current_fullness / self.buffer_size as f64,
279        }
280    }
281}
282
283/// Buffer utilization metrics.
284#[derive(Clone, Debug)]
285pub struct BufferUtilization {
286    /// Minimum fullness ratio observed.
287    pub min_ratio: f64,
288    /// Maximum fullness ratio observed.
289    pub max_ratio: f64,
290    /// Current fullness ratio.
291    pub current_ratio: f64,
292}
293
294#[cfg(test)]
295mod tests {
296    use super::*;
297
298    #[test]
299    fn test_vbv_config_new() {
300        let config = VbvConfig::new(1_000_000, 5_000_000, 30, 1);
301        assert_eq!(config.buffer_size, 1_000_000);
302        assert_eq!(config.max_bitrate, 5_000_000);
303        assert_eq!(config.initial_fullness, 0.75);
304    }
305
306    #[test]
307    fn test_vbv_config_frame_period() {
308        let config = VbvConfig::new(1_000_000, 5_000_000, 30, 1);
309        let period = config.frame_period();
310        assert!((period - 1.0 / 30.0).abs() < 1e-6);
311    }
312
313    #[test]
314    fn test_vbv_buffer_new() {
315        let config = VbvConfig::new(1_000_000, 5_000_000, 30, 1);
316        let buffer = VbvBuffer::new(config);
317        assert_eq!(buffer.current_fullness, 750_000.0);
318    }
319
320    #[test]
321    fn test_vbv_buffer_can_send() {
322        let config = VbvConfig::new(1_000_000, 5_000_000, 30, 1);
323        let buffer = VbvBuffer::new(config);
324
325        assert!(buffer.can_send_frame(100_000));
326        assert!(buffer.can_send_frame(750_000));
327        assert!(!buffer.can_send_frame(800_000));
328    }
329
330    #[test]
331    fn test_vbv_buffer_update() {
332        let config = VbvConfig::new(1_000_000, 5_000_000, 30, 1);
333        let mut buffer = VbvBuffer::new(config);
334
335        let result = buffer.update(100_000);
336        assert!(!result.underflow);
337        assert!(!result.overflow);
338        assert!(result.fullness_ratio > 0.0);
339    }
340
341    #[test]
342    fn test_vbv_buffer_underflow() {
343        let config = VbvConfig::new(1_000_000, 5_000_000, 30, 1);
344        let mut buffer = VbvBuffer::new(config);
345
346        // Try to send more than available
347        let result = buffer.update(1_000_000);
348        assert!(result.underflow);
349        assert_eq!(buffer.underflow_count, 1);
350    }
351
352    #[test]
353    fn test_vbv_target_frame_size() {
354        let config = VbvConfig::new(1_000_000, 5_000_000, 30, 1);
355        let buffer = VbvBuffer::new(config);
356
357        let target = buffer.target_frame_size(FrameType::Inter, 50_000.0);
358        assert!(target > 0);
359
360        let key_target = buffer.target_frame_size(FrameType::Key, 50_000.0);
361        assert!(key_target > target); // Keyframes should be larger
362    }
363
364    #[test]
365    fn test_vbv_statistics() {
366        let config = VbvConfig::new(1_000_000, 5_000_000, 30, 1);
367        let mut buffer = VbvBuffer::new(config);
368
369        buffer.update(100_000);
370        buffer.update(100_000);
371
372        let stats = buffer.statistics();
373        assert_eq!(stats.frame_count, 2);
374        assert!(stats.is_compliant());
375    }
376
377    #[test]
378    fn test_vbv_reset() {
379        let config = VbvConfig::new(1_000_000, 5_000_000, 30, 1);
380        let mut buffer = VbvBuffer::new(config);
381
382        buffer.update(100_000);
383        assert_eq!(buffer.frame_count, 1);
384
385        buffer.reset();
386        assert_eq!(buffer.frame_count, 0);
387        assert_eq!(buffer.current_fullness, 750_000.0);
388    }
389}