Skip to main content

oximedia_codec/av1/
loop_filter.rs

1//! AV1 Loop Filter parameters.
2//!
3//! The loop filter is applied after transform reconstruction to reduce
4//! blocking artifacts at block boundaries. AV1 uses a direction-adaptive
5//! loop filter with separate parameters for each edge type.
6//!
7//! # Loop Filter Parameters
8//!
9//! - Filter level (0-63) per plane and direction
10//! - Sharpness (0-7) affects filter threshold
11//! - Delta values for mode and reference frame adjustments
12//!
13//! # Reference
14//!
15//! See AV1 Specification Section 5.9.11 for loop filter syntax and
16//! Section 7.14 for loop filter semantics.
17
18#![forbid(unsafe_code)]
19#![allow(dead_code)]
20#![allow(clippy::doc_markdown)]
21#![allow(clippy::unused_self)]
22#![allow(clippy::cast_possible_truncation)]
23#![allow(clippy::trivially_copy_pass_by_ref)]
24#![allow(clippy::cast_sign_loss)]
25#![allow(clippy::unnecessary_cast)]
26#![allow(clippy::identity_op)]
27#![allow(clippy::if_not_else)]
28#![allow(clippy::missing_errors_doc)]
29
30use super::sequence::SequenceHeader;
31use crate::error::{CodecError, CodecResult};
32use oximedia_io::BitReader;
33
34// =============================================================================
35// Constants
36// =============================================================================
37
38/// Maximum loop filter level.
39pub const MAX_LOOP_FILTER_LEVEL: u8 = 63;
40
41/// Maximum sharpness level.
42pub const MAX_SHARPNESS_LEVEL: u8 = 7;
43
44/// Number of loop filter mode deltas.
45pub const MAX_MODE_LF_DELTAS: usize = 2;
46
47/// Number of reference frame deltas (including intra).
48pub const TOTAL_REFS_PER_FRAME: usize = 8;
49
50/// Loop filter level bits in bitstream.
51pub const LF_LEVEL_BITS: u8 = 6;
52
53/// Loop filter delta bits in bitstream.
54pub const LF_DELTA_BITS: u8 = 6;
55
56/// Default reference deltas.
57pub const DEFAULT_REF_DELTAS: [i8; TOTAL_REFS_PER_FRAME] = [1, 0, 0, 0, 0, -1, -1, -1];
58
59/// Default mode deltas.
60pub const DEFAULT_MODE_DELTAS: [i8; MAX_MODE_LF_DELTAS] = [0, 0];
61
62// =============================================================================
63// Structures
64// =============================================================================
65
66/// Loop filter parameters as parsed from the frame header.
67#[derive(Clone, Debug)]
68pub struct LoopFilterParams {
69    /// Loop filter level for Y vertical edges.
70    pub level: [u8; 4],
71    /// Sharpness level (0-7).
72    pub sharpness: u8,
73    /// Delta coding enabled.
74    pub delta_enabled: bool,
75    /// Update delta values.
76    pub delta_update: bool,
77    /// Reference frame deltas.
78    pub ref_deltas: [i8; TOTAL_REFS_PER_FRAME],
79    /// Mode deltas (for ZERO_MV and MV modes).
80    pub mode_deltas: [i8; MAX_MODE_LF_DELTAS],
81}
82
83impl Default for LoopFilterParams {
84    fn default() -> Self {
85        Self {
86            level: [0; 4],
87            sharpness: 0,
88            delta_enabled: true,
89            delta_update: true,
90            ref_deltas: DEFAULT_REF_DELTAS,
91            mode_deltas: DEFAULT_MODE_DELTAS,
92        }
93    }
94}
95
96impl LoopFilterParams {
97    /// Create a new loop filter params with default values.
98    #[must_use]
99    pub fn new() -> Self {
100        Self::default()
101    }
102
103    /// Parse loop filter parameters from the bitstream.
104    ///
105    /// # Errors
106    ///
107    /// Returns error if the bitstream is malformed.
108    #[allow(clippy::cast_possible_truncation)]
109    pub fn parse(
110        reader: &mut BitReader<'_>,
111        seq: &SequenceHeader,
112        frame_is_intra: bool,
113    ) -> CodecResult<Self> {
114        let mut lf = Self::new();
115
116        // Read filter levels
117        lf.level[0] = reader.read_bits(LF_LEVEL_BITS).map_err(CodecError::Core)? as u8;
118        lf.level[1] = reader.read_bits(LF_LEVEL_BITS).map_err(CodecError::Core)? as u8;
119
120        // Chroma levels only if there are chroma planes and Y levels > 0
121        if !seq.color_config.mono_chrome && (lf.level[0] > 0 || lf.level[1] > 0) {
122            lf.level[2] = reader.read_bits(LF_LEVEL_BITS).map_err(CodecError::Core)? as u8;
123            lf.level[3] = reader.read_bits(LF_LEVEL_BITS).map_err(CodecError::Core)? as u8;
124        }
125
126        // Sharpness
127        lf.sharpness = reader.read_bits(3).map_err(CodecError::Core)? as u8;
128
129        // Delta coding
130        lf.delta_enabled = reader.read_bit().map_err(CodecError::Core)? != 0;
131
132        if lf.delta_enabled {
133            lf.delta_update = reader.read_bit().map_err(CodecError::Core)? != 0;
134
135            if lf.delta_update {
136                // Reference deltas
137                for i in 0..TOTAL_REFS_PER_FRAME {
138                    let update = reader.read_bit().map_err(CodecError::Core)? != 0;
139                    if update {
140                        lf.ref_deltas[i] = Self::read_delta(reader)?;
141                    }
142                }
143
144                // Mode deltas (only for inter frames)
145                if !frame_is_intra {
146                    for i in 0..MAX_MODE_LF_DELTAS {
147                        let update = reader.read_bit().map_err(CodecError::Core)? != 0;
148                        if update {
149                            lf.mode_deltas[i] = Self::read_delta(reader)?;
150                        }
151                    }
152                }
153            }
154        }
155
156        Ok(lf)
157    }
158
159    /// Read a signed delta value using su(1+6) format.
160    #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
161    fn read_delta(reader: &mut BitReader<'_>) -> CodecResult<i8> {
162        let abs_value = reader.read_bits(LF_DELTA_BITS).map_err(CodecError::Core)? as i8;
163        if abs_value != 0 {
164            let sign = reader.read_bit().map_err(CodecError::Core)?;
165            if sign != 0 {
166                Ok(-abs_value)
167            } else {
168                Ok(abs_value)
169            }
170        } else {
171            Ok(0)
172        }
173    }
174
175    /// Get the Y vertical filter level.
176    #[must_use]
177    pub const fn level_y_v(&self) -> u8 {
178        self.level[0]
179    }
180
181    /// Get the Y horizontal filter level.
182    #[must_use]
183    pub const fn level_y_h(&self) -> u8 {
184        self.level[1]
185    }
186
187    /// Get the U filter level.
188    #[must_use]
189    pub const fn level_u(&self) -> u8 {
190        self.level[2]
191    }
192
193    /// Get the V filter level.
194    #[must_use]
195    pub const fn level_v(&self) -> u8 {
196        self.level[3]
197    }
198
199    /// Check if loop filter is enabled for any plane.
200    #[must_use]
201    pub fn is_enabled(&self) -> bool {
202        self.level.iter().any(|&l| l > 0)
203    }
204
205    /// Get the filter level for a specific plane and direction.
206    ///
207    /// # Arguments
208    ///
209    /// * `plane` - Plane index (0=Y, 1=U, 2=V)
210    /// * `direction` - 0 for vertical, 1 for horizontal
211    #[must_use]
212    pub fn get_level(&self, plane: usize, direction: usize) -> u8 {
213        match (plane, direction) {
214            (0, 0) => self.level[0],
215            (0, 1) => self.level[1],
216            (1, _) => self.level[2],
217            (2, _) => self.level[3],
218            _ => 0,
219        }
220    }
221
222    /// Compute the filter level for a block.
223    ///
224    /// This applies delta adjustments based on reference frame and mode.
225    ///
226    /// # Arguments
227    ///
228    /// * `base_level` - Base filter level from frame header
229    /// * `ref_frame` - Reference frame index (0 for intra)
230    /// * `mode` - Mode index (0 for ZEROMV, 1 for other MV modes)
231    /// * `segment_delta` - Delta from segmentation
232    #[must_use]
233    #[allow(clippy::cast_sign_loss, clippy::cast_possible_wrap)]
234    pub fn compute_level(
235        &self,
236        base_level: u8,
237        ref_frame: usize,
238        mode: usize,
239        segment_delta: i16,
240    ) -> u8 {
241        if base_level == 0 || !self.delta_enabled {
242            return base_level;
243        }
244
245        let mut level = i32::from(base_level);
246
247        // Apply segmentation delta
248        level += i32::from(segment_delta);
249
250        // Apply reference frame delta
251        if ref_frame < TOTAL_REFS_PER_FRAME {
252            level += i32::from(self.ref_deltas[ref_frame]);
253        }
254
255        // Apply mode delta (only for inter blocks)
256        if ref_frame > 0 && mode < MAX_MODE_LF_DELTAS {
257            level += i32::from(self.mode_deltas[mode]);
258        }
259
260        level.clamp(0, i32::from(MAX_LOOP_FILTER_LEVEL)) as u8
261    }
262
263    /// Get the limit value for the filter based on sharpness.
264    ///
265    /// The limit determines the maximum difference for the filter to be applied.
266    #[must_use]
267    pub fn get_limit(&self, level: u8) -> u8 {
268        if self.sharpness > 0 {
269            let block_limit = (9 - self.sharpness).max(1);
270            let shift = (self.sharpness + 3) >> 2;
271            ((level >> shift) as u8).min(block_limit)
272        } else {
273            ((level >> 0) as u8).max(1)
274        }
275    }
276
277    /// Get the threshold value for the filter.
278    #[must_use]
279    pub fn get_threshold(&self, level: u8) -> u8 {
280        // thresh = 0.5 * limit
281        self.get_limit(level) >> 1
282    }
283
284    /// Get the high edge variance threshold.
285    #[must_use]
286    pub const fn get_hev_threshold(&self, level: u8) -> u8 {
287        if level >= 40 {
288            2
289        } else if level >= 20 {
290            1
291        } else {
292            0
293        }
294    }
295
296    /// Check if delta updates are present.
297    #[must_use]
298    pub const fn has_delta_updates(&self) -> bool {
299        self.delta_enabled && self.delta_update
300    }
301
302    /// Reset deltas to default values.
303    pub fn reset_deltas(&mut self) {
304        self.ref_deltas = DEFAULT_REF_DELTAS;
305        self.mode_deltas = DEFAULT_MODE_DELTAS;
306    }
307
308    /// Set filter level for all planes.
309    pub fn set_level_all(&mut self, level: u8) {
310        self.level = [level; 4];
311    }
312
313    /// Serialize loop filter parameters to bitstream.
314    #[must_use]
315    pub fn to_bytes(&self, seq: &SequenceHeader, frame_is_intra: bool) -> Vec<u8> {
316        let mut bits: Vec<u8> = Vec::new();
317
318        // This is a simplified serialization
319        // In practice, you would write bits to a BitWriter
320
321        bits.push(self.level[0]);
322        bits.push(self.level[1]);
323
324        if !seq.color_config.mono_chrome && (self.level[0] > 0 || self.level[1] > 0) {
325            bits.push(self.level[2]);
326            bits.push(self.level[3]);
327        }
328
329        bits.push(self.sharpness);
330        bits.push(u8::from(self.delta_enabled));
331
332        if self.delta_enabled {
333            bits.push(u8::from(self.delta_update));
334
335            if self.delta_update {
336                for &delta in &self.ref_deltas {
337                    #[allow(clippy::cast_sign_loss)]
338                    bits.push(delta.unsigned_abs());
339                }
340                if !frame_is_intra {
341                    for &delta in &self.mode_deltas {
342                        #[allow(clippy::cast_sign_loss)]
343                        bits.push(delta.unsigned_abs());
344                    }
345                }
346            }
347        }
348
349        bits
350    }
351}
352
353/// Loop filter edge information.
354#[derive(Clone, Copy, Debug, Default)]
355pub struct LoopFilterEdge {
356    /// Edge direction (0=vertical, 1=horizontal).
357    pub direction: u8,
358    /// Filter level for this edge.
359    pub level: u8,
360    /// Limit value.
361    pub limit: u8,
362    /// Threshold value.
363    pub threshold: u8,
364    /// High edge variance threshold.
365    pub hev_threshold: u8,
366}
367
368impl LoopFilterEdge {
369    /// Create a new loop filter edge with the given parameters.
370    #[must_use]
371    pub fn new(params: &LoopFilterParams, level: u8, direction: u8) -> Self {
372        Self {
373            direction,
374            level,
375            limit: params.get_limit(level),
376            threshold: params.get_threshold(level),
377            hev_threshold: params.get_hev_threshold(level),
378        }
379    }
380
381    /// Check if filtering should be applied.
382    #[must_use]
383    pub const fn should_filter(&self) -> bool {
384        self.level > 0
385    }
386}
387
388/// Loop filter mask for a superblock.
389///
390/// Contains bitmasks indicating which edges need filtering.
391#[derive(Clone, Debug, Default)]
392pub struct LoopFilterMask {
393    /// Vertical edge masks for each transform size.
394    pub left_y: [u64; 4],
395    /// Horizontal edge masks for each transform size.
396    pub above_y: [u64; 4],
397    /// Vertical edge masks for U plane.
398    pub left_u: [u16; 4],
399    /// Horizontal edge masks for U plane.
400    pub above_u: [u16; 4],
401    /// Vertical edge masks for V plane.
402    pub left_v: [u16; 4],
403    /// Horizontal edge masks for V plane.
404    pub above_v: [u16; 4],
405}
406
407impl LoopFilterMask {
408    /// Create a new empty loop filter mask.
409    #[must_use]
410    pub fn new() -> Self {
411        Self::default()
412    }
413
414    /// Set a bit in the Y vertical mask.
415    pub fn set_left_y(&mut self, tx_size: usize, row: usize, col: usize, sb_size: usize) {
416        if tx_size < 4 && row < sb_size && col < sb_size {
417            let bit = row * sb_size + col;
418            if bit < 64 {
419                self.left_y[tx_size] |= 1u64 << bit;
420            }
421        }
422    }
423
424    /// Set a bit in the Y horizontal mask.
425    pub fn set_above_y(&mut self, tx_size: usize, row: usize, col: usize, sb_size: usize) {
426        if tx_size < 4 && row < sb_size && col < sb_size {
427            let bit = row * sb_size + col;
428            if bit < 64 {
429                self.above_y[tx_size] |= 1u64 << bit;
430            }
431        }
432    }
433
434    /// Clear all masks.
435    pub fn clear(&mut self) {
436        *self = Self::default();
437    }
438
439    /// Check if any edges need filtering.
440    #[must_use]
441    pub fn has_edges(&self) -> bool {
442        self.left_y.iter().any(|&m| m != 0)
443            || self.above_y.iter().any(|&m| m != 0)
444            || self.left_u.iter().any(|&m| m != 0)
445            || self.above_u.iter().any(|&m| m != 0)
446            || self.left_v.iter().any(|&m| m != 0)
447            || self.above_v.iter().any(|&m| m != 0)
448    }
449}
450
451/// Loop filter context for block-level decisions.
452#[derive(Clone, Debug, Default)]
453pub struct LoopFilterContext {
454    /// Level lookup table indexed by reference frame and mode.
455    level_lookup: [[u8; MAX_MODE_LF_DELTAS + 1]; TOTAL_REFS_PER_FRAME],
456}
457
458impl LoopFilterContext {
459    /// Create a new loop filter context.
460    #[must_use]
461    pub fn new() -> Self {
462        Self::default()
463    }
464
465    /// Initialize the level lookup table.
466    #[allow(clippy::cast_sign_loss, clippy::cast_possible_wrap)]
467    pub fn init(&mut self, params: &LoopFilterParams, base_level: u8) {
468        if base_level == 0 || !params.delta_enabled {
469            for ref_frame in 0..TOTAL_REFS_PER_FRAME {
470                for mode in 0..=MAX_MODE_LF_DELTAS {
471                    self.level_lookup[ref_frame][mode] = base_level;
472                }
473            }
474            return;
475        }
476
477        for ref_frame in 0..TOTAL_REFS_PER_FRAME {
478            for mode in 0..=MAX_MODE_LF_DELTAS {
479                let mut level = i32::from(base_level);
480                level += i32::from(params.ref_deltas[ref_frame]);
481                if ref_frame > 0 && mode < MAX_MODE_LF_DELTAS {
482                    level += i32::from(params.mode_deltas[mode]);
483                }
484                self.level_lookup[ref_frame][mode] =
485                    level.clamp(0, i32::from(MAX_LOOP_FILTER_LEVEL)) as u8;
486            }
487        }
488    }
489
490    /// Get the filter level for a block.
491    #[must_use]
492    pub fn get_level(&self, ref_frame: usize, mode: usize) -> u8 {
493        if ref_frame < TOTAL_REFS_PER_FRAME && mode <= MAX_MODE_LF_DELTAS {
494            self.level_lookup[ref_frame][mode]
495        } else {
496            0
497        }
498    }
499}
500
501// =============================================================================
502// Tests
503// =============================================================================
504
505#[cfg(test)]
506mod tests {
507    use super::*;
508
509    #[test]
510    fn test_loop_filter_default() {
511        let lf = LoopFilterParams::default();
512        assert_eq!(lf.level[0], 0);
513        assert_eq!(lf.sharpness, 0);
514        assert!(lf.delta_enabled);
515        assert!(!lf.is_enabled());
516    }
517
518    #[test]
519    fn test_loop_filter_is_enabled() {
520        let mut lf = LoopFilterParams::default();
521        assert!(!lf.is_enabled());
522
523        lf.level[0] = 10;
524        assert!(lf.is_enabled());
525
526        lf.level[0] = 0;
527        lf.level[2] = 5;
528        assert!(lf.is_enabled());
529    }
530
531    #[test]
532    fn test_loop_filter_get_level() {
533        let mut lf = LoopFilterParams::default();
534        lf.level = [10, 20, 30, 40];
535
536        assert_eq!(lf.get_level(0, 0), 10); // Y vertical
537        assert_eq!(lf.get_level(0, 1), 20); // Y horizontal
538        assert_eq!(lf.get_level(1, 0), 30); // U
539        assert_eq!(lf.get_level(1, 1), 30); // U (both directions same)
540        assert_eq!(lf.get_level(2, 0), 40); // V
541        assert_eq!(lf.get_level(3, 0), 0); // Invalid plane
542    }
543
544    #[test]
545    fn test_loop_filter_accessors() {
546        let mut lf = LoopFilterParams::default();
547        lf.level = [10, 20, 30, 40];
548
549        assert_eq!(lf.level_y_v(), 10);
550        assert_eq!(lf.level_y_h(), 20);
551        assert_eq!(lf.level_u(), 30);
552        assert_eq!(lf.level_v(), 40);
553    }
554
555    #[test]
556    fn test_compute_level() {
557        let mut lf = LoopFilterParams::default();
558        lf.delta_enabled = true;
559        lf.ref_deltas = [1, -1, 2, 0, 0, 0, 0, 0];
560        lf.mode_deltas = [0, -2];
561
562        // Base level only
563        let level = lf.compute_level(30, 0, 0, 0);
564        assert_eq!(level, 31); // 30 + ref_delta[0]=1
565
566        // With mode delta
567        let level = lf.compute_level(30, 1, 1, 0);
568        assert_eq!(level, 27); // 30 + ref_delta[1]=-1 + mode_delta[1]=-2
569
570        // With segmentation delta
571        let level = lf.compute_level(30, 0, 0, 10);
572        assert_eq!(level, 41); // 30 + 10 + ref_delta[0]=1
573
574        // Clamping to max
575        let level = lf.compute_level(60, 0, 0, 10);
576        assert_eq!(level, 63); // Clamped to MAX_LOOP_FILTER_LEVEL
577    }
578
579    #[test]
580    fn test_compute_level_disabled() {
581        let mut lf = LoopFilterParams::default();
582        lf.delta_enabled = false;
583        lf.ref_deltas[0] = 10;
584
585        // Delta disabled, should return base level
586        let level = lf.compute_level(30, 0, 0, 0);
587        assert_eq!(level, 30);
588
589        // Base level 0 returns 0
590        let level = lf.compute_level(0, 0, 0, 0);
591        assert_eq!(level, 0);
592    }
593
594    #[test]
595    fn test_get_limit() {
596        let mut lf = LoopFilterParams::default();
597
598        // Sharpness 0
599        lf.sharpness = 0;
600        assert_eq!(lf.get_limit(30), 30);
601
602        // Sharpness > 0 reduces limit
603        lf.sharpness = 4;
604        let limit = lf.get_limit(32);
605        assert!(limit <= 32);
606        assert!(limit <= 9 - 4);
607    }
608
609    #[test]
610    fn test_get_threshold() {
611        let lf = LoopFilterParams::default();
612        let limit = lf.get_limit(20);
613        let threshold = lf.get_threshold(20);
614        assert_eq!(threshold, limit >> 1);
615    }
616
617    #[test]
618    fn test_get_hev_threshold() {
619        let lf = LoopFilterParams::default();
620
621        assert_eq!(lf.get_hev_threshold(10), 0);
622        assert_eq!(lf.get_hev_threshold(25), 1);
623        assert_eq!(lf.get_hev_threshold(45), 2);
624    }
625
626    #[test]
627    fn test_has_delta_updates() {
628        let mut lf = LoopFilterParams::default();
629        lf.delta_enabled = true;
630        lf.delta_update = true;
631        assert!(lf.has_delta_updates());
632
633        lf.delta_enabled = false;
634        assert!(!lf.has_delta_updates());
635
636        lf.delta_enabled = true;
637        lf.delta_update = false;
638        assert!(!lf.has_delta_updates());
639    }
640
641    #[test]
642    fn test_reset_deltas() {
643        let mut lf = LoopFilterParams::default();
644        lf.ref_deltas = [10; TOTAL_REFS_PER_FRAME];
645        lf.mode_deltas = [5; MAX_MODE_LF_DELTAS];
646
647        lf.reset_deltas();
648
649        assert_eq!(lf.ref_deltas, DEFAULT_REF_DELTAS);
650        assert_eq!(lf.mode_deltas, DEFAULT_MODE_DELTAS);
651    }
652
653    #[test]
654    fn test_set_level_all() {
655        let mut lf = LoopFilterParams::default();
656        lf.set_level_all(25);
657
658        assert_eq!(lf.level, [25, 25, 25, 25]);
659    }
660
661    #[test]
662    fn test_loop_filter_edge() {
663        let params = LoopFilterParams::default();
664        let edge = LoopFilterEdge::new(&params, 30, 0);
665
666        assert_eq!(edge.level, 30);
667        assert_eq!(edge.direction, 0);
668        assert!(edge.should_filter());
669
670        let edge_zero = LoopFilterEdge::new(&params, 0, 1);
671        assert!(!edge_zero.should_filter());
672    }
673
674    #[test]
675    fn test_loop_filter_mask() {
676        let mut mask = LoopFilterMask::new();
677        assert!(!mask.has_edges());
678
679        mask.set_left_y(0, 0, 0, 8);
680        assert!(mask.has_edges());
681        assert_eq!(mask.left_y[0], 1);
682
683        mask.set_above_y(1, 2, 3, 8);
684        assert_eq!(mask.above_y[1], 1u64 << (2 * 8 + 3));
685
686        mask.clear();
687        assert!(!mask.has_edges());
688    }
689
690    #[test]
691    fn test_loop_filter_context() {
692        let mut params = LoopFilterParams::default();
693        params.delta_enabled = true;
694        params.ref_deltas = [2, -1, 0, 0, 0, 0, 0, 0];
695        params.mode_deltas = [0, -3];
696
697        let mut ctx = LoopFilterContext::new();
698        ctx.init(&params, 30);
699
700        assert_eq!(ctx.get_level(0, 0), 32); // 30 + 2
701        assert_eq!(ctx.get_level(1, 0), 29); // 30 - 1 + 0 (mode delta only for inter)
702        assert_eq!(ctx.get_level(1, 1), 26); // 30 - 1 - 3
703    }
704
705    #[test]
706    fn test_loop_filter_context_disabled() {
707        let mut params = LoopFilterParams::default();
708        params.delta_enabled = false;
709
710        let mut ctx = LoopFilterContext::new();
711        ctx.init(&params, 30);
712
713        // All levels should be base level when delta is disabled
714        assert_eq!(ctx.get_level(0, 0), 30);
715        assert_eq!(ctx.get_level(1, 1), 30);
716    }
717
718    #[test]
719    fn test_default_deltas() {
720        assert_eq!(DEFAULT_REF_DELTAS, [1, 0, 0, 0, 0, -1, -1, -1]);
721        assert_eq!(DEFAULT_MODE_DELTAS, [0, 0]);
722    }
723
724    #[test]
725    fn test_constants() {
726        assert_eq!(MAX_LOOP_FILTER_LEVEL, 63);
727        assert_eq!(MAX_SHARPNESS_LEVEL, 7);
728        assert_eq!(LF_LEVEL_BITS, 6);
729        assert_eq!(LF_DELTA_BITS, 6);
730    }
731}