1#![forbid(unsafe_code)]
17#![allow(dead_code)]
18#![allow(clippy::cast_possible_truncation)]
19#![allow(clippy::cast_precision_loss)]
20#![allow(clippy::cast_sign_loss)]
21#![allow(clippy::similar_names)]
22
23use super::block::BlockSize;
24use super::cdef::{CdefParams, CdefStrength};
25use super::loop_filter::LoopFilterParams;
26
27const MAX_LOOP_FILTER_LEVEL: u8 = 63;
33
34const MAX_CDEF_STRENGTH: u8 = 15;
36
37const FILTER_LEVELS_TO_TEST: usize = 5;
39
40const CDEF_STRENGTHS_TO_TEST: usize = 4;
42
43#[derive(Clone, Debug)]
49pub struct LoopFilterOptimizer {
50 params: LoopFilterParams,
52 lambda: f32,
54 rd_optimization: bool,
56}
57
58impl LoopFilterOptimizer {
59 #[must_use]
61 pub fn new(lambda: f32) -> Self {
62 Self {
63 params: LoopFilterParams::default(),
64 lambda,
65 rd_optimization: true,
66 }
67 }
68
69 pub fn optimize_filter_level(
73 &mut self,
74 src: &[u8],
75 recon: &[u8],
76 width: usize,
77 height: usize,
78 qp: u8,
79 ) -> u8 {
80 if !self.rd_optimization {
81 return self.filter_level_from_qp(qp);
83 }
84
85 let base_level = self.filter_level_from_qp(qp);
86 let mut best_level = base_level;
87 let mut best_cost = f32::MAX;
88
89 for delta in -(FILTER_LEVELS_TO_TEST as i32 / 2)..=(FILTER_LEVELS_TO_TEST as i32 / 2) {
91 let level = (i32::from(base_level) + delta * 4)
92 .clamp(0, i32::from(MAX_LOOP_FILTER_LEVEL)) as u8;
93
94 let cost = self.evaluate_filter_level(src, recon, width, height, level);
95
96 if cost < best_cost {
97 best_cost = cost;
98 best_level = level;
99 }
100 }
101
102 self.params.level = [best_level, best_level, best_level, best_level];
103 best_level
104 }
105
106 fn filter_level_from_qp(&self, qp: u8) -> u8 {
108 ((i32::from(qp) * 3) / 2).clamp(0, i32::from(MAX_LOOP_FILTER_LEVEL)) as u8
110 }
111
112 fn evaluate_filter_level(
114 &self,
115 src: &[u8],
116 recon: &[u8],
117 width: usize,
118 height: usize,
119 level: u8,
120 ) -> f32 {
121 let distortion = self.compute_distortion(src, recon, width, height);
123
124 let rate = f32::from(level) * 0.1;
126
127 distortion + self.lambda * rate
128 }
129
130 fn compute_distortion(&self, src: &[u8], recon: &[u8], width: usize, height: usize) -> f32 {
132 let mut sse = 0u64;
133 let total = (width * height).min(src.len()).min(recon.len());
134
135 for i in 0..total {
136 let diff = i32::from(src[i]) - i32::from(recon[i]);
137 sse += (diff * diff) as u64;
138 }
139
140 sse as f32
141 }
142
143 #[must_use]
145 pub const fn params(&self) -> &LoopFilterParams {
146 &self.params
147 }
148
149 pub fn set_lambda(&mut self, lambda: f32) {
151 self.lambda = lambda;
152 }
153
154 pub fn set_rd_optimization(&mut self, enabled: bool) {
156 self.rd_optimization = enabled;
157 }
158}
159
160impl Default for LoopFilterOptimizer {
161 fn default() -> Self {
162 Self::new(1.0)
163 }
164}
165
166#[derive(Clone, Debug)]
172pub struct CdefOptimizer {
173 params: CdefParams,
175 lambda: f32,
177}
178
179impl CdefOptimizer {
180 #[must_use]
182 pub fn new(lambda: f32) -> Self {
183 Self {
184 params: CdefParams::default(),
185 lambda,
186 }
187 }
188
189 pub fn optimize_strength(
191 &mut self,
192 src: &[u8],
193 recon: &[u8],
194 width: usize,
195 height: usize,
196 _block_size: BlockSize,
197 ) -> CdefStrength {
198 let mut best_strength = CdefStrength::default();
199 let mut best_cost = f32::MAX;
200
201 for primary in 0..CDEF_STRENGTHS_TO_TEST {
202 for secondary in 0..CDEF_STRENGTHS_TO_TEST {
203 let strength = CdefStrength {
204 primary: primary as u8,
205 secondary: secondary as u8,
206 };
207
208 let cost = self.evaluate_cdef_strength(src, recon, width, height, &strength);
209
210 if cost < best_cost {
211 best_cost = cost;
212 best_strength = strength;
213 }
214 }
215 }
216
217 best_strength
218 }
219
220 fn evaluate_cdef_strength(
222 &self,
223 src: &[u8],
224 recon: &[u8],
225 width: usize,
226 height: usize,
227 strength: &CdefStrength,
228 ) -> f32 {
229 let mut sse = 0u64;
231 let total = (width * height).min(src.len()).min(recon.len());
232
233 for i in 0..total {
234 let diff = i32::from(src[i]) - i32::from(recon[i]);
235 sse += (diff * diff) as u64;
236 }
237
238 let distortion = sse as f32;
239
240 let rate = f32::from(strength.primary + strength.secondary) * 0.5;
242
243 distortion + self.lambda * rate
244 }
245
246 #[must_use]
248 pub const fn params(&self) -> &CdefParams {
249 &self.params
250 }
251}
252
253impl Default for CdefOptimizer {
254 fn default() -> Self {
255 Self::new(1.0)
256 }
257}
258
259#[derive(Clone, Copy, Debug, PartialEq, Eq)]
265pub enum RestorationType {
266 None = 0,
268 Wiener = 1,
270 Sgrproj = 2,
272}
273
274#[derive(Clone, Debug)]
276pub struct RestorationOptimizer {
277 restoration_type: RestorationType,
279 lambda: f32,
281}
282
283impl RestorationOptimizer {
284 #[must_use]
286 pub fn new(lambda: f32) -> Self {
287 Self {
288 restoration_type: RestorationType::None,
289 lambda,
290 }
291 }
292
293 pub fn optimize_restoration(
295 &mut self,
296 src: &[u8],
297 recon: &[u8],
298 width: usize,
299 height: usize,
300 ) -> RestorationType {
301 let mut best_type = RestorationType::None;
302 let mut best_cost = f32::MAX;
303
304 for rtype in [
305 RestorationType::None,
306 RestorationType::Wiener,
307 RestorationType::Sgrproj,
308 ] {
309 let cost = self.evaluate_restoration(src, recon, width, height, rtype);
310
311 if cost < best_cost {
312 best_cost = cost;
313 best_type = rtype;
314 }
315 }
316
317 self.restoration_type = best_type;
318 best_type
319 }
320
321 fn evaluate_restoration(
323 &self,
324 src: &[u8],
325 recon: &[u8],
326 width: usize,
327 height: usize,
328 rtype: RestorationType,
329 ) -> f32 {
330 let base_distortion = self.compute_distortion(src, recon, width, height);
332
333 let rate = match rtype {
334 RestorationType::None => 0.0,
335 RestorationType::Wiener => 100.0,
336 RestorationType::Sgrproj => 80.0,
337 };
338
339 let distortion_reduction = match rtype {
340 RestorationType::None => 0.0,
341 RestorationType::Wiener => base_distortion * 0.05,
342 RestorationType::Sgrproj => base_distortion * 0.03,
343 };
344
345 (base_distortion - distortion_reduction) + self.lambda * rate
346 }
347
348 fn compute_distortion(&self, src: &[u8], recon: &[u8], width: usize, height: usize) -> f32 {
350 let mut sse = 0u64;
351 let total = (width * height).min(src.len()).min(recon.len());
352
353 for i in 0..total {
354 let diff = i32::from(src[i]) - i32::from(recon[i]);
355 sse += (diff * diff) as u64;
356 }
357
358 sse as f32
359 }
360
361 #[must_use]
363 pub const fn restoration_type(&self) -> RestorationType {
364 self.restoration_type
365 }
366}
367
368impl Default for RestorationOptimizer {
369 fn default() -> Self {
370 Self::new(1.0)
371 }
372}
373
374#[derive(Clone, Debug, Default)]
380pub struct FilmGrainParams {
381 pub enabled: bool,
383 pub grain_seed: u16,
385 pub luma_points: Vec<(u8, u8)>,
387 pub chroma_points: Vec<(u8, u8)>,
389}
390
391impl FilmGrainParams {
392 #[must_use]
394 pub const fn new() -> Self {
395 Self {
396 enabled: false,
397 grain_seed: 0,
398 luma_points: Vec::new(),
399 chroma_points: Vec::new(),
400 }
401 }
402
403 pub fn enable(&mut self, seed: u16) {
405 self.enabled = true;
406 self.grain_seed = seed;
407 }
408
409 pub fn disable(&mut self) {
411 self.enabled = false;
412 }
413}
414
415#[derive(Clone, Debug)]
421pub struct LoopOptimizer {
422 loop_filter: LoopFilterOptimizer,
424 cdef: CdefOptimizer,
426 restoration: RestorationOptimizer,
428 film_grain: FilmGrainParams,
430}
431
432impl LoopOptimizer {
433 #[must_use]
435 pub fn new(lambda: f32) -> Self {
436 Self {
437 loop_filter: LoopFilterOptimizer::new(lambda),
438 cdef: CdefOptimizer::new(lambda),
439 restoration: RestorationOptimizer::new(lambda),
440 film_grain: FilmGrainParams::new(),
441 }
442 }
443
444 pub fn optimize_frame(
446 &mut self,
447 src: &[u8],
448 recon: &[u8],
449 width: usize,
450 height: usize,
451 qp: u8,
452 ) {
453 self.loop_filter
455 .optimize_filter_level(src, recon, width, height, qp);
456
457 let cdef_width = width.min(64);
459 let cdef_height = height.min(64);
460 self.cdef
461 .optimize_strength(src, recon, cdef_width, cdef_height, BlockSize::Block64x64);
462
463 self.restoration
465 .optimize_restoration(src, recon, width, height);
466 }
467
468 #[must_use]
470 pub const fn loop_filter_params(&self) -> &LoopFilterParams {
471 self.loop_filter.params()
472 }
473
474 #[must_use]
476 pub const fn cdef_params(&self) -> &CdefParams {
477 self.cdef.params()
478 }
479
480 #[must_use]
482 pub const fn restoration_type(&self) -> RestorationType {
483 self.restoration.restoration_type()
484 }
485
486 #[must_use]
488 pub const fn film_grain_params(&self) -> &FilmGrainParams {
489 &self.film_grain
490 }
491
492 pub fn set_lambda(&mut self, lambda: f32) {
494 self.loop_filter.set_lambda(lambda);
495 self.cdef.lambda = lambda;
496 self.restoration.lambda = lambda;
497 }
498}
499
500impl Default for LoopOptimizer {
501 fn default() -> Self {
502 Self::new(1.0)
503 }
504}
505
506#[cfg(test)]
511mod tests {
512 use super::*;
513
514 #[test]
515 fn test_loop_filter_optimizer_creation() {
516 let opt = LoopFilterOptimizer::new(1.0);
517 assert_eq!(opt.lambda, 1.0);
518 assert!(opt.rd_optimization);
519 }
520
521 #[test]
522 fn test_filter_level_from_qp() {
523 let opt = LoopFilterOptimizer::new(1.0);
524
525 let level_low = opt.filter_level_from_qp(10);
526 let level_high = opt.filter_level_from_qp(50);
527
528 assert!(level_low < level_high);
529 assert!(level_low <= MAX_LOOP_FILTER_LEVEL);
530 assert!(level_high <= MAX_LOOP_FILTER_LEVEL);
531 }
532
533 #[test]
534 fn test_optimize_filter_level_fast() {
535 let mut opt = LoopFilterOptimizer::new(1.0);
536 opt.set_rd_optimization(false);
537
538 let src = vec![128u8; 64 * 64];
539 let recon = vec![128u8; 64 * 64];
540
541 let level = opt.optimize_filter_level(&src, &recon, 64, 64, 28);
542 assert!(level <= MAX_LOOP_FILTER_LEVEL);
543 }
544
545 #[test]
546 fn test_compute_distortion() {
547 let opt = LoopFilterOptimizer::new(1.0);
548
549 let src = vec![100u8; 64];
550 let recon = vec![100u8; 64];
551
552 let distortion = opt.compute_distortion(&src, &recon, 8, 8);
553 assert_eq!(distortion, 0.0);
554
555 let recon2 = vec![110u8; 64];
556 let distortion2 = opt.compute_distortion(&src, &recon2, 8, 8);
557 assert!(distortion2 > 0.0);
558 }
559
560 #[test]
561 fn test_cdef_optimizer() {
562 let opt = CdefOptimizer::new(1.0);
563 assert_eq!(opt.lambda, 1.0);
564 }
565
566 #[test]
567 fn test_cdef_optimize_strength() {
568 let mut opt = CdefOptimizer::new(1.0);
569
570 let src = vec![128u8; 32 * 32];
571 let recon = vec![130u8; 32 * 32];
572
573 let strength = opt.optimize_strength(&src, &recon, 32, 32, BlockSize::Block32x32);
574
575 assert!(strength.primary <= MAX_CDEF_STRENGTH);
576 assert!(strength.secondary <= MAX_CDEF_STRENGTH);
577 }
578
579 #[test]
580 fn test_restoration_optimizer() {
581 let opt = RestorationOptimizer::new(1.0);
582 assert_eq!(opt.restoration_type, RestorationType::None);
583 }
584
585 #[test]
586 fn test_restoration_optimize() {
587 let mut opt = RestorationOptimizer::new(1.0);
588
589 let src = vec![128u8; 64 * 64];
590 let recon = vec![130u8; 64 * 64];
591
592 let rtype = opt.optimize_restoration(&src, &recon, 64, 64);
593 assert!(matches!(
594 rtype,
595 RestorationType::None | RestorationType::Wiener | RestorationType::Sgrproj
596 ));
597 }
598
599 #[test]
600 fn test_film_grain_params() {
601 let mut params = FilmGrainParams::new();
602 assert!(!params.enabled);
603
604 params.enable(1234);
605 assert!(params.enabled);
606 assert_eq!(params.grain_seed, 1234);
607
608 params.disable();
609 assert!(!params.enabled);
610 }
611
612 #[test]
613 fn test_combined_optimizer() {
614 let opt = LoopOptimizer::new(1.5);
615 assert_eq!(opt.loop_filter.lambda, 1.5);
616 assert_eq!(opt.cdef.lambda, 1.5);
617 }
618
619 #[test]
620 fn test_combined_optimize_frame() {
621 let mut opt = LoopOptimizer::new(1.0);
622
623 let src = vec![128u8; 128 * 128];
624 let recon = vec![128u8; 128 * 128];
625
626 opt.optimize_frame(&src, &recon, 128, 128, 28);
627
628 let lf_params = opt.loop_filter_params();
630 assert!(lf_params.level[0] <= MAX_LOOP_FILTER_LEVEL);
631 }
632
633 #[test]
634 fn test_set_lambda() {
635 let mut opt = LoopOptimizer::new(1.0);
636 opt.set_lambda(2.5);
637
638 assert_eq!(opt.loop_filter.lambda, 2.5);
639 assert_eq!(opt.cdef.lambda, 2.5);
640 assert_eq!(opt.restoration.lambda, 2.5);
641 }
642
643 #[test]
644 fn test_restoration_types() {
645 assert_eq!(RestorationType::None as u8, 0);
646 assert_eq!(RestorationType::Wiener as u8, 1);
647 assert_eq!(RestorationType::Sgrproj as u8, 2);
648 }
649
650 #[test]
651 fn test_constants() {
652 assert_eq!(MAX_LOOP_FILTER_LEVEL, 63);
653 assert_eq!(MAX_CDEF_STRENGTH, 15);
654 assert!(FILTER_LEVELS_TO_TEST > 0);
655 assert!(CDEF_STRENGTHS_TO_TEST > 0);
656 }
657}