oximedia_optimize/transform/
quant.rs1#[derive(Debug, Clone, Copy)]
5pub struct AdaptiveQp {
6 pub base_qp: u8,
8 pub offset: i8,
10 pub effective_qp: u8,
12}
13
14impl AdaptiveQp {
15 #[must_use]
17 pub fn new(base_qp: u8, offset: i8) -> Self {
18 let effective_qp = (i16::from(base_qp) + i16::from(offset)).clamp(0, 63) as u8;
19
20 Self {
21 base_qp,
22 offset,
23 effective_qp,
24 }
25 }
26}
27
28pub struct QuantizationOptimizer {
30 enable_trellis: bool,
31 deadzone_offset: f64,
32}
33
34impl Default for QuantizationOptimizer {
35 fn default() -> Self {
36 Self::new(false, 0.0)
37 }
38}
39
40impl QuantizationOptimizer {
41 #[must_use]
43 pub fn new(enable_trellis: bool, deadzone_offset: f64) -> Self {
44 Self {
45 enable_trellis,
46 deadzone_offset,
47 }
48 }
49
50 #[allow(dead_code)]
52 #[must_use]
53 pub fn quantize(&self, coeffs: &[i16], qp: u8) -> Vec<i16> {
54 if self.enable_trellis {
55 self.trellis_quantize(coeffs, qp)
56 } else {
57 self.simple_quantize(coeffs, qp)
58 }
59 }
60
61 fn simple_quantize(&self, coeffs: &[i16], qp: u8) -> Vec<i16> {
62 let scale = self.qp_to_scale(qp);
63 let deadzone = (f64::from(scale) * self.deadzone_offset) as i16;
64
65 coeffs
66 .iter()
67 .map(|&c| if c.abs() < deadzone { 0 } else { c / scale })
68 .collect()
69 }
70
71 fn trellis_quantize(&self, coeffs: &[i16], qp: u8) -> Vec<i16> {
72 let scale = self.qp_to_scale(qp);
75 let mut quantized = vec![0i16; coeffs.len()];
76
77 for (i, &c) in coeffs.iter().enumerate() {
78 let q = c / scale;
79
80 let candidates = [q.saturating_sub(1), q, q.saturating_add(1)];
82 let mut best_q = q;
83 let mut best_cost = f64::MAX;
84
85 for &candidate in &candidates {
86 let reconstructed = candidate * scale;
87 let distortion = (c - reconstructed).abs();
88 let rate = if candidate == 0 { 0 } else { candidate.abs() };
89 let cost = f64::from(distortion) + f64::from(rate);
90
91 if cost < best_cost {
92 best_cost = cost;
93 best_q = candidate;
94 }
95 }
96
97 quantized[i] = best_q;
98 }
99
100 quantized
101 }
102
103 fn qp_to_scale(&self, qp: u8) -> i16 {
104 (1 << (qp / 6)).max(1)
107 }
108
109 #[allow(dead_code)]
111 #[must_use]
112 pub fn calculate_deadzone(&self, variance: f64) -> f64 {
113 if variance < 100.0 {
115 1.5
116 } else if variance < 500.0 {
117 1.0
118 } else {
119 0.5
120 }
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn test_adaptive_qp() {
130 let qp = AdaptiveQp::new(26, 3);
131 assert_eq!(qp.base_qp, 26);
132 assert_eq!(qp.offset, 3);
133 assert_eq!(qp.effective_qp, 29);
134 }
135
136 #[test]
137 fn test_adaptive_qp_clamping() {
138 let qp_high = AdaptiveQp::new(60, 10);
139 assert_eq!(qp_high.effective_qp, 63); let qp_low = AdaptiveQp::new(5, -10);
142 assert_eq!(qp_low.effective_qp, 0); }
144
145 #[test]
146 fn test_quantization_optimizer_creation() {
147 let optimizer = QuantizationOptimizer::default();
148 assert!(!optimizer.enable_trellis);
149 assert_eq!(optimizer.deadzone_offset, 0.0);
150 }
151
152 #[test]
153 fn test_simple_quantize() {
154 let optimizer = QuantizationOptimizer::default();
155 let coeffs = vec![100, 50, 25, 10, 5];
156 let quantized = optimizer.simple_quantize(&coeffs, 12);
157 assert!(quantized.iter().all(|&q| q <= 100));
158 }
159
160 #[test]
161 fn test_deadzone_calculation() {
162 let optimizer = QuantizationOptimizer::default();
163 let dz_low = optimizer.calculate_deadzone(50.0);
164 let dz_high = optimizer.calculate_deadzone(1000.0);
165 assert!(dz_low > dz_high); }
167}