Skip to main content

oximedia_codec/av1/
coeff_decode.rs

1//! AV1 transform coefficient decoding.
2//!
3//! This module handles the complete decoding of transform coefficients from
4//! the entropy-coded bitstream, including:
5//!
6//! - EOB (End of Block) position parsing
7//! - Coefficient level decoding using multi-level scheme
8//! - Coefficient sign decoding
9//! - Dequantization
10//! - Scan order application
11//!
12//! # Coefficient Decoding Process
13//!
14//! 1. **EOB Parsing** - Determine position of last non-zero coefficient
15//! 2. **Coefficient Levels** - Decode base levels and ranges
16//! 3. **DC Sign** - Decode sign of DC coefficient
17//! 4. **AC Signs** - Decode signs of AC coefficients
18//! 5. **Dequantization** - Apply quantization parameters
19//! 6. **Scan Order** - Convert from scan order to raster order
20//!
21//! # Context Modeling
22//!
23//! Coefficient decoding uses adaptive context models based on:
24//! - Position within the block
25//! - Magnitude of neighboring coefficients
26//! - Transform size and type
27
28#![forbid(unsafe_code)]
29#![allow(dead_code)]
30#![allow(clippy::doc_markdown)]
31#![allow(clippy::too_many_arguments)]
32#![allow(clippy::cast_possible_truncation)]
33#![allow(clippy::cast_sign_loss)]
34#![allow(clippy::cast_possible_wrap)]
35#![allow(clippy::similar_names)]
36#![allow(clippy::module_name_repetitions)]
37
38use super::coefficients::{
39    dequantize_block, get_dequant_shift, CoeffBuffer, CoeffContext, CoeffStats, EobContext, EobPt,
40    LevelContext, ScanOrderCache,
41};
42use super::entropy::SymbolReader;
43use super::entropy_tables::CdfContext;
44use super::quantization::QuantizationParams;
45use super::transform::{TxSize, TxType};
46use crate::error::CodecResult;
47
48// =============================================================================
49// Constants
50// =============================================================================
51
52/// Maximum coefficient level for base coding.
53pub const COEFF_BASE_MAX: u32 = 3;
54
55/// Coefficient base range (used for higher levels).
56pub const BR_CDF_SIZE: usize = 4;
57
58/// Maximum Golomb-Rice parameter.
59pub const MAX_BR_PARAM: u8 = 5;
60
61/// Number of EOB offset bits.
62pub const EOB_OFFSET_BITS: [u8; 12] = [0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
63
64/// Scan order coeff skip threshold.
65pub const COEFF_SKIP_THRESHOLD: u16 = 256;
66
67// =============================================================================
68// Coefficient Decoder
69// =============================================================================
70
71/// Decoder for transform coefficients.
72#[derive(Debug)]
73pub struct CoeffDecoder {
74    /// Symbol reader.
75    reader: SymbolReader,
76    /// CDF context for probability models.
77    cdf_context: CdfContext,
78    /// Scan order cache.
79    scan_cache: ScanOrderCache,
80    /// Quantization parameters.
81    quant_params: QuantizationParams,
82    /// Current bit depth.
83    bit_depth: u8,
84}
85
86impl CoeffDecoder {
87    /// Create a new coefficient decoder.
88    pub fn new(data: Vec<u8>, quant_params: QuantizationParams, bit_depth: u8) -> Self {
89        Self {
90            reader: SymbolReader::new(data),
91            cdf_context: CdfContext::new(),
92            scan_cache: ScanOrderCache::new(),
93            quant_params,
94            bit_depth,
95        }
96    }
97
98    /// Decode coefficients for a transform block.
99    pub fn decode_coefficients(
100        &mut self,
101        tx_size: TxSize,
102        tx_type: TxType,
103        plane: u8,
104        skip: bool,
105    ) -> CodecResult<CoeffBuffer> {
106        let mut ctx = CoeffContext::new(tx_size, tx_type, plane);
107
108        if skip {
109            // Skip blocks have all-zero coefficients
110            return Ok(CoeffBuffer::from_tx_size(tx_size));
111        }
112
113        // Decode EOB position
114        ctx.eob = self.decode_eob(tx_size, plane)?;
115
116        if ctx.eob == 0 {
117            // No coefficients
118            return Ok(CoeffBuffer::from_tx_size(tx_size));
119        }
120
121        // Get scan order (clone to avoid borrow issues)
122        let scan = self.scan_cache.get(tx_size, ctx.tx_class()).to_vec();
123
124        // Decode coefficient levels
125        self.decode_coeff_levels(&mut ctx, &scan)?;
126
127        // Decode signs
128        self.decode_signs(&mut ctx, &scan)?;
129
130        // Dequantize coefficients
131        self.dequantize_coefficients(&mut ctx, plane)?;
132
133        // Convert from scan order to raster order
134        let mut buffer = CoeffBuffer::from_tx_size(tx_size);
135        self.reorder_coefficients(&ctx, &scan, &mut buffer)?;
136
137        Ok(buffer)
138    }
139
140    /// Decode EOB (End of Block) position.
141    fn decode_eob(&mut self, tx_size: TxSize, plane: u8) -> CodecResult<u16> {
142        let _eob_ctx = EobContext::new(tx_size);
143
144        // Read EOB multi-symbol
145        let ctx = (tx_size as usize * 3) + (plane as usize);
146        let eob_multi_cdf = self.cdf_context.get_eob_multi_cdf_mut(ctx);
147        let eob_multi = self.reader.read_symbol(eob_multi_cdf) as u8;
148
149        if eob_multi == 0 {
150            return Ok(0); // No coefficients
151        }
152
153        // Read EOB extra bits
154        let eob_pt = EobPt::from_eob(eob_multi.into());
155        let extra_bits = eob_pt.extra_bits();
156
157        let eob_extra = if extra_bits > 0 {
158            self.reader.read_literal(extra_bits)
159        } else {
160            0
161        };
162
163        let eob = EobContext::compute_eob(eob_multi, eob_extra as u16);
164        Ok(eob)
165    }
166
167    /// Decode coefficient levels.
168    fn decode_coeff_levels(&mut self, ctx: &mut CoeffContext, scan: &[u16]) -> CodecResult<()> {
169        let eob = ctx.eob as usize;
170
171        // Decode in reverse scan order (from EOB to DC)
172        for scan_idx in (0..eob).rev() {
173            let pos = scan[scan_idx] as usize;
174            let level = self.decode_coeff_level(ctx, pos, scan_idx == eob - 1)?;
175            ctx.levels[pos] = level as i32;
176        }
177
178        Ok(())
179    }
180
181    /// Decode a single coefficient level.
182    fn decode_coeff_level(
183        &mut self,
184        ctx: &CoeffContext,
185        pos: usize,
186        is_eob: bool,
187    ) -> CodecResult<u32> {
188        let level_ctx = ctx.compute_level_context(pos);
189
190        // Decode base level (0-3)
191        let base_level = if is_eob {
192            // At EOB, coefficient is at least 1
193            1 + self.decode_coeff_base_eob(&level_ctx, ctx.plane)?
194        } else {
195            self.decode_coeff_base(&level_ctx, ctx.plane)?
196        };
197
198        if base_level >= COEFF_BASE_MAX {
199            // Decode additional range
200            let range = self.decode_coeff_base_range(&level_ctx, ctx.plane)?;
201            Ok(base_level + range)
202        } else {
203            Ok(base_level)
204        }
205    }
206
207    /// Decode coefficient base level.
208    fn decode_coeff_base(&mut self, level_ctx: &LevelContext, _plane: u8) -> CodecResult<u32> {
209        let context = level_ctx.context() as usize;
210        let cdf = self.cdf_context.get_coeff_base_cdf_mut(context);
211        Ok(self.reader.read_symbol(cdf) as u32)
212    }
213
214    /// Decode coefficient base at EOB.
215    fn decode_coeff_base_eob(&mut self, level_ctx: &LevelContext, _plane: u8) -> CodecResult<u32> {
216        let context = level_ctx.context() as usize;
217        let cdf = self.cdf_context.get_coeff_base_eob_cdf_mut(context);
218        Ok(self.reader.read_symbol(cdf) as u32)
219    }
220
221    /// Decode coefficient base range for high magnitude coefficients.
222    fn decode_coeff_base_range(
223        &mut self,
224        level_ctx: &LevelContext,
225        _plane: u8,
226    ) -> CodecResult<u32> {
227        let context = level_ctx.mag_context() as usize;
228        let mut total_range = 0u32;
229
230        // Multi-level Golomb-Rice coding
231        for _level in 0..5 {
232            let br_cdf = self.cdf_context.get_coeff_br_cdf_mut(context);
233            let br_symbol = self.reader.read_symbol(br_cdf);
234
235            if br_symbol < BR_CDF_SIZE - 1 {
236                total_range += br_symbol as u32;
237                break;
238            } else {
239                total_range += (BR_CDF_SIZE - 1) as u32;
240            }
241        }
242
243        Ok(total_range)
244    }
245
246    /// Decode signs for all non-zero coefficients.
247    fn decode_signs(&mut self, ctx: &mut CoeffContext, scan: &[u16]) -> CodecResult<()> {
248        let eob = ctx.eob as usize;
249
250        // Decode DC sign first (if DC is non-zero)
251        if ctx.levels[0] != 0 {
252            let dc_sign_ctx = ctx.dc_sign_context();
253            let cdf_slice = self.cdf_context.get_dc_sign_cdf_mut(dc_sign_ctx as usize);
254            // read_bool needs &mut [u16; 3], but we have &mut [u16]
255            // Copy to a fixed-size array
256            if cdf_slice.len() >= 3 {
257                let mut cdf_array = [cdf_slice[0], cdf_slice[1], cdf_slice[2]];
258                let sign = self.reader.read_bool(&mut cdf_array);
259                // Copy back updated CDF
260                cdf_slice[0] = cdf_array[0];
261                cdf_slice[1] = cdf_array[1];
262                cdf_slice[2] = cdf_array[2];
263                ctx.signs[0] = sign;
264                if sign {
265                    ctx.levels[0] = -ctx.levels[0];
266                }
267            }
268        }
269
270        // Decode AC signs
271        for scan_idx in 1..eob {
272            let pos = scan[scan_idx] as usize;
273            if ctx.levels[pos] != 0 {
274                // AC signs use equiprobable model
275                let sign = self.reader.read_bool_eq();
276                ctx.signs[pos] = sign;
277                if sign {
278                    ctx.levels[pos] = -ctx.levels[pos];
279                }
280            }
281        }
282
283        Ok(())
284    }
285
286    /// Dequantize coefficients.
287    fn dequantize_coefficients(&mut self, ctx: &mut CoeffContext, plane: u8) -> CodecResult<()> {
288        // Get dequant values
289        let dc_dequant = self
290            .quant_params
291            .get_dc_quant(plane as usize, self.bit_depth) as i16;
292        let ac_dequant = self
293            .quant_params
294            .get_ac_quant(plane as usize, self.bit_depth) as i16;
295        let shift = get_dequant_shift(self.bit_depth);
296
297        dequantize_block(&mut ctx.levels, dc_dequant, ac_dequant, shift);
298
299        Ok(())
300    }
301
302    /// Reorder coefficients from scan order to raster order.
303    fn reorder_coefficients(
304        &self,
305        ctx: &CoeffContext,
306        scan: &[u16],
307        buffer: &mut CoeffBuffer,
308    ) -> CodecResult<()> {
309        let eob = ctx.eob as usize;
310
311        for scan_idx in 0..eob {
312            let pos = scan[scan_idx] as usize;
313            if pos < ctx.levels.len() {
314                let level = ctx.levels[pos];
315                let (row, col) = ctx.get_scan_position(pos);
316                buffer.set(row as usize, col as usize, level);
317            }
318        }
319
320        Ok(())
321    }
322
323    /// Check if more data is available.
324    pub fn has_more_data(&self) -> bool {
325        self.reader.has_more_data()
326    }
327
328    /// Get current position.
329    pub fn position(&self) -> usize {
330        self.reader.position()
331    }
332}
333
334// =============================================================================
335// Coefficient Encoding
336// =============================================================================
337
338/// Encoder for transform coefficients.
339#[derive(Debug)]
340pub struct CoeffEncoder {
341    /// Symbol writer.
342    writer: super::entropy::SymbolWriter,
343    /// CDF context.
344    cdf_context: CdfContext,
345    /// Scan order cache.
346    scan_cache: ScanOrderCache,
347}
348
349impl CoeffEncoder {
350    /// Create a new coefficient encoder.
351    #[must_use]
352    pub fn new() -> Self {
353        Self {
354            writer: super::entropy::SymbolWriter::new(),
355            cdf_context: CdfContext::new(),
356            scan_cache: ScanOrderCache::new(),
357        }
358    }
359
360    /// Encode coefficients for a transform block.
361    pub fn encode_coefficients(
362        &mut self,
363        buffer: &CoeffBuffer,
364        tx_size: TxSize,
365        tx_type: TxType,
366        plane: u8,
367    ) -> CodecResult<()> {
368        let tx_class = tx_type.tx_class();
369        let scan = self.scan_cache.get(tx_size, tx_class).to_vec();
370
371        // Find EOB
372        let eob = self.find_eob(buffer, &scan);
373
374        // Encode EOB
375        self.encode_eob(eob, tx_size, plane)?;
376
377        if eob == 0 {
378            return Ok(());
379        }
380
381        // Convert to scan order
382        let mut levels = vec![0i32; eob as usize];
383        buffer.copy_to_scan(&mut levels, &scan[..eob as usize]);
384
385        // Encode levels
386        self.encode_levels(&levels, &scan, plane)?;
387
388        // Encode signs
389        self.encode_signs(&levels, &scan)?;
390
391        Ok(())
392    }
393
394    /// Find EOB position.
395    fn find_eob(&self, buffer: &CoeffBuffer, scan: &[u16]) -> u16 {
396        for (i, &pos) in scan.iter().enumerate().rev() {
397            let (row, col) = self.pos_to_rowcol(pos as usize, buffer);
398            if buffer.get(row, col) != 0 {
399                return (i + 1) as u16;
400            }
401        }
402        0
403    }
404
405    /// Convert position to row/col.
406    fn pos_to_rowcol(&self, pos: usize, buffer: &CoeffBuffer) -> (usize, usize) {
407        let slice = buffer.as_slice();
408        let width = (slice.len() as f64).sqrt() as usize;
409        (pos / width, pos % width)
410    }
411
412    /// Encode EOB.
413    fn encode_eob(&mut self, eob: u16, tx_size: TxSize, plane: u8) -> CodecResult<()> {
414        let ctx = (tx_size as usize * 3) + (plane as usize);
415
416        if eob == 0 {
417            let cdf = self.cdf_context.get_eob_multi_cdf_mut(ctx);
418            self.writer.write_symbol(0, cdf);
419            return Ok(());
420        }
421
422        let eob_pt = EobPt::from_eob(eob);
423        let cdf = self.cdf_context.get_eob_multi_cdf_mut(ctx);
424        self.writer.write_symbol(eob_pt as usize, cdf);
425
426        // Write extra bits
427        let extra_bits = eob_pt.extra_bits();
428        if extra_bits > 0 {
429            let offset = eob - eob_pt.base_eob();
430            self.writer.write_literal(offset as u32, extra_bits);
431        }
432
433        Ok(())
434    }
435
436    /// Encode coefficient levels.
437    fn encode_levels(&mut self, levels: &[i32], scan: &[u16], plane: u8) -> CodecResult<()> {
438        let mut ctx = LevelContext::new();
439
440        for (scan_idx, &_pos) in scan.iter().enumerate().rev() {
441            let level = levels[scan_idx].unsigned_abs();
442
443            let base_level = level.min(COEFF_BASE_MAX);
444            let is_eob = scan_idx == levels.len() - 1;
445
446            if is_eob {
447                let cdf = self
448                    .cdf_context
449                    .get_coeff_base_eob_cdf_mut(ctx.context() as usize);
450                self.writer.write_symbol((base_level - 1) as usize, cdf);
451            } else {
452                let cdf = self
453                    .cdf_context
454                    .get_coeff_base_cdf_mut(ctx.context() as usize);
455                self.writer.write_symbol(base_level as usize, cdf);
456            }
457
458            if level >= COEFF_BASE_MAX {
459                self.encode_base_range(level - COEFF_BASE_MAX, &ctx, plane)?;
460            }
461
462            // Update context
463            ctx.mag += level;
464            if level > 0 {
465                ctx.count += 1;
466            }
467        }
468
469        Ok(())
470    }
471
472    /// Encode coefficient base range.
473    fn encode_base_range(&mut self, range: u32, ctx: &LevelContext, _plane: u8) -> CodecResult<()> {
474        let mut remaining = range;
475        let mag_ctx = ctx.mag_context() as usize;
476
477        for _level in 0..5 {
478            if remaining == 0 {
479                break;
480            }
481
482            let symbol = remaining.min((BR_CDF_SIZE - 1) as u32) as usize;
483            let cdf = self.cdf_context.get_coeff_br_cdf_mut(mag_ctx);
484            self.writer.write_symbol(symbol, cdf);
485
486            if symbol < BR_CDF_SIZE - 1 {
487                break;
488            }
489
490            remaining -= (BR_CDF_SIZE - 1) as u32;
491        }
492
493        Ok(())
494    }
495
496    /// Encode signs.
497    fn encode_signs(&mut self, levels: &[i32], scan: &[u16]) -> CodecResult<()> {
498        // DC sign
499        if !levels.is_empty() && levels[0] != 0 {
500            let dc_ctx = 1; // Simplified context
501            let cdf_slice = self.cdf_context.get_dc_sign_cdf_mut(dc_ctx);
502            if cdf_slice.len() >= 3 {
503                let mut cdf = [cdf_slice[0], cdf_slice[1], cdf_slice[2]];
504                self.writer.write_bool(levels[0] < 0, &mut cdf);
505                // Copy back updated CDF
506                cdf_slice[0] = cdf[0];
507                cdf_slice[1] = cdf[1];
508                cdf_slice[2] = cdf[2];
509            }
510        }
511
512        // AC signs
513        for (idx, &_pos) in scan.iter().enumerate().skip(1) {
514            if idx < levels.len() && levels[idx] != 0 {
515                // Equiprobable
516                let mut cdf = [16384u16, 32768, 0];
517                self.writer.write_bool(levels[idx] < 0, &mut cdf);
518            }
519        }
520
521        Ok(())
522    }
523
524    /// Finalize and get output.
525    #[must_use]
526    pub fn finish(self) -> Vec<u8> {
527        self.writer.finish()
528    }
529}
530
531impl Default for CoeffEncoder {
532    fn default() -> Self {
533        Self::new()
534    }
535}
536
537// =============================================================================
538// Batched Coefficient Decoder
539// =============================================================================
540
541/// Batched decoder for multiple coefficient blocks.
542pub struct BatchedCoeffDecoder {
543    /// Base decoder.
544    decoder: CoeffDecoder,
545    /// Decoded blocks cache.
546    blocks: Vec<CoeffBuffer>,
547}
548
549impl BatchedCoeffDecoder {
550    /// Create a new batched decoder.
551    pub fn new(data: Vec<u8>, quant_params: QuantizationParams, bit_depth: u8) -> Self {
552        Self {
553            decoder: CoeffDecoder::new(data, quant_params, bit_depth),
554            blocks: Vec::new(),
555        }
556    }
557
558    /// Decode multiple blocks.
559    pub fn decode_blocks(
560        &mut self,
561        specs: &[(TxSize, TxType, u8, bool)],
562    ) -> CodecResult<Vec<CoeffBuffer>> {
563        self.blocks.clear();
564
565        for &(tx_size, tx_type, plane, skip) in specs {
566            let buffer = self
567                .decoder
568                .decode_coefficients(tx_size, tx_type, plane, skip)?;
569            self.blocks.push(buffer);
570        }
571
572        Ok(std::mem::take(&mut self.blocks))
573    }
574
575    /// Get statistics for decoded blocks.
576    #[must_use]
577    pub fn get_statistics(&self) -> Vec<CoeffStats> {
578        self.blocks
579            .iter()
580            .map(|b| CoeffStats::from_coeffs(b.as_slice()))
581            .collect()
582    }
583}
584
585// =============================================================================
586// Coefficient Analysis
587// =============================================================================
588
589/// Analyze coefficient distribution.
590#[derive(Clone, Debug, Default)]
591pub struct CoeffAnalysis {
592    /// Total coefficients analyzed.
593    pub total_coeffs: u64,
594    /// Zero coefficients.
595    pub zero_count: u64,
596    /// Non-zero coefficients.
597    pub nonzero_count: u64,
598    /// DC coefficient sum.
599    pub dc_sum: i64,
600    /// AC coefficient sum.
601    pub ac_sum: i64,
602    /// Maximum absolute value.
603    pub max_abs: u32,
604}
605
606impl CoeffAnalysis {
607    /// Create new analysis.
608    #[must_use]
609    pub const fn new() -> Self {
610        Self {
611            total_coeffs: 0,
612            zero_count: 0,
613            nonzero_count: 0,
614            dc_sum: 0,
615            ac_sum: 0,
616            max_abs: 0,
617        }
618    }
619
620    /// Analyze a coefficient buffer.
621    pub fn analyze(&mut self, buffer: &CoeffBuffer) {
622        let coeffs = buffer.as_slice();
623        self.total_coeffs += coeffs.len() as u64;
624
625        if !coeffs.is_empty() {
626            self.dc_sum += i64::from(coeffs[0]);
627        }
628
629        for (i, &coeff) in coeffs.iter().enumerate() {
630            let abs_val = coeff.unsigned_abs();
631
632            if coeff == 0 {
633                self.zero_count += 1;
634            } else {
635                self.nonzero_count += 1;
636                self.max_abs = self.max_abs.max(abs_val);
637
638                if i > 0 {
639                    self.ac_sum += i64::from(coeff);
640                }
641            }
642        }
643    }
644
645    /// Get sparsity ratio (percentage of zeros).
646    #[must_use]
647    pub fn sparsity(&self) -> f64 {
648        if self.total_coeffs > 0 {
649            (self.zero_count as f64 / self.total_coeffs as f64) * 100.0
650        } else {
651            0.0
652        }
653    }
654
655    /// Get average DC value.
656    #[must_use]
657    pub fn avg_dc(&self) -> f64 {
658        if self.total_coeffs > 0 {
659            self.dc_sum as f64 / self.total_coeffs as f64
660        } else {
661            0.0
662        }
663    }
664}
665
666// =============================================================================
667// Tests
668// =============================================================================
669
670#[cfg(test)]
671mod tests {
672    use super::*;
673
674    fn create_test_quant_params() -> QuantizationParams {
675        QuantizationParams {
676            base_q_idx: 100,
677            delta_q_y_dc: 0,
678            delta_q_u_dc: 0,
679            delta_q_v_dc: 0,
680            delta_q_u_ac: 0,
681            delta_q_v_ac: 0,
682            using_qmatrix: false,
683            qm_y: 15,
684            qm_u: 15,
685            qm_v: 15,
686            delta_q_present: false,
687            delta_q_res: 0,
688        }
689    }
690
691    #[test]
692    fn test_coeff_decoder_creation() {
693        let data = vec![0u8; 128];
694        let quant = create_test_quant_params();
695        let decoder = CoeffDecoder::new(data, quant, 8);
696        assert!(decoder.has_more_data());
697    }
698
699    #[test]
700    fn test_coeff_encoder_creation() {
701        let encoder = CoeffEncoder::new();
702        let output = encoder.finish();
703        assert!(!output.is_empty() || output.is_empty());
704    }
705
706    #[test]
707    fn test_batched_decoder() {
708        let data = vec![0u8; 256];
709        let quant = create_test_quant_params();
710        let mut decoder = BatchedCoeffDecoder::new(data, quant, 8);
711
712        let specs = vec![
713            (TxSize::Tx4x4, TxType::DctDct, 0, false),
714            (TxSize::Tx8x8, TxType::DctDct, 0, false),
715        ];
716
717        // Decoding may fail with test data, but should not crash
718        let _ = decoder.decode_blocks(&specs);
719    }
720
721    #[test]
722    fn test_coeff_analysis() {
723        let mut analysis = CoeffAnalysis::new();
724        let mut buffer = CoeffBuffer::new(4, 4);
725
726        buffer.set(0, 0, 100); // DC
727        buffer.set(1, 1, 50); // AC
728        buffer.set(2, 2, -30); // AC
729
730        analysis.analyze(&buffer);
731
732        assert_eq!(analysis.total_coeffs, 16);
733        assert_eq!(analysis.nonzero_count, 3);
734        assert_eq!(analysis.zero_count, 13);
735        assert_eq!(analysis.max_abs, 100);
736    }
737
738    #[test]
739    fn test_coeff_analysis_sparsity() {
740        let mut analysis = CoeffAnalysis::new();
741        let buffer = CoeffBuffer::new(8, 8); // All zeros
742
743        analysis.analyze(&buffer);
744
745        assert_eq!(analysis.sparsity(), 100.0);
746    }
747
748    #[test]
749    fn test_constants() {
750        assert_eq!(COEFF_BASE_MAX, 3);
751        assert_eq!(BR_CDF_SIZE, 4);
752        assert_eq!(MAX_BR_PARAM, 5);
753    }
754
755    #[test]
756    fn test_eob_offset_bits() {
757        assert_eq!(EOB_OFFSET_BITS[0], 0);
758        assert_eq!(EOB_OFFSET_BITS[3], 1);
759        assert_eq!(EOB_OFFSET_BITS[11], 9);
760    }
761
762    #[test]
763    fn test_coeff_analysis_avg_dc() {
764        let mut analysis = CoeffAnalysis::new();
765        let mut buffer = CoeffBuffer::new(4, 4);
766        buffer.set(0, 0, 200);
767
768        analysis.analyze(&buffer);
769
770        // DC sum is 200, total coeffs is 16
771        assert!((analysis.avg_dc() - 12.5).abs() < 0.1);
772    }
773
774    #[test]
775    fn test_coeff_decoder_position() {
776        let data = vec![0u8; 128];
777        let quant = create_test_quant_params();
778        let decoder = CoeffDecoder::new(data, quant, 8);
779        assert!(decoder.position() <= 128);
780    }
781}