Skip to main content

oximedia_codec/av1/
quantization.rs

1//! AV1 Quantization parameters.
2//!
3//! Quantization controls the precision of transform coefficients. AV1 uses
4//! adaptive quantization with separate parameters for DC and AC coefficients,
5//! and supports per-plane delta quantization.
6//!
7//! # Quantization Parameters
8//!
9//! - Base Q index (0-255): Primary quantization level
10//! - Delta Q values: Adjustments for DC and AC, per plane
11//! - Quantization matrices (QM): Optional matrices for coefficient weighting
12//! - Delta Q resolution: Precision for per-block delta Q
13//!
14//! # Dequantization
15//!
16//! The dequantizer values are derived from lookup tables based on the
17//! effective Q index (base + delta).
18//!
19//! # Reference
20//!
21//! See AV1 Specification Section 5.9.12 for quantization syntax and
22//! Section 7.12 for quantization semantics.
23
24#![forbid(unsafe_code)]
25#![allow(dead_code)]
26#![allow(clippy::doc_markdown)]
27#![allow(clippy::unnecessary_min_or_max)]
28#![allow(clippy::unused_self)]
29#![allow(clippy::cast_possible_truncation)]
30#![allow(clippy::trivially_copy_pass_by_ref)]
31#![allow(clippy::missing_errors_doc)]
32
33use super::sequence::SequenceHeader;
34use crate::error::{CodecError, CodecResult};
35use oximedia_io::BitReader;
36
37// =============================================================================
38// Constants
39// =============================================================================
40
41/// Maximum Q index value.
42pub const MAX_Q_INDEX: u8 = 255;
43
44/// Minimum Q index value.
45pub const MIN_Q_INDEX: u8 = 0;
46
47/// Number of Q index values.
48pub const QINDEX_RANGE: usize = 256;
49
50/// Maximum delta Q value (in absolute terms).
51pub const MAX_DELTA_Q: i8 = 63;
52
53/// Minimum delta Q value (in absolute terms).
54pub const MIN_DELTA_Q: i8 = -64;
55
56/// Delta Q bits in bitstream.
57pub const DELTA_Q_BITS: u8 = 6;
58
59/// Number of QM levels.
60pub const NUM_QM_LEVELS: usize = 16;
61
62// =============================================================================
63// DC and AC Dequantizer Tables (8-bit)
64// =============================================================================
65
66/// DC dequantizer lookup table for 8-bit depth.
67pub const DC_QLOOKUP: [i16; QINDEX_RANGE] = [
68    4, 8, 8, 9, 10, 11, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 23, 24, 25, 26, 26, 27,
69    28, 29, 30, 31, 32, 32, 33, 34, 35, 36, 37, 38, 38, 39, 40, 41, 42, 43, 43, 44, 45, 46, 47, 48,
70    48, 49, 50, 51, 52, 53, 53, 54, 55, 56, 57, 57, 58, 59, 60, 61, 62, 62, 63, 64, 65, 66, 66, 67,
71    68, 69, 70, 70, 71, 72, 73, 74, 74, 75, 76, 77, 78, 78, 79, 80, 81, 81, 82, 83, 84, 85, 85, 87,
72    88, 90, 92, 93, 95, 96, 98, 99, 101, 102, 104, 105, 107, 108, 110, 111, 113, 114, 116, 117,
73    118, 120, 121, 123, 125, 127, 129, 131, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154,
74    156, 158, 161, 164, 166, 169, 172, 174, 177, 180, 182, 185, 187, 190, 192, 195, 199, 202, 205,
75    208, 211, 214, 217, 220, 223, 226, 230, 233, 237, 240, 243, 247, 250, 253, 257, 261, 265, 269,
76    272, 276, 280, 284, 288, 292, 296, 300, 304, 309, 313, 317, 322, 326, 330, 335, 340, 344, 349,
77    354, 359, 364, 369, 374, 379, 384, 389, 395, 400, 406, 411, 417, 423, 429, 435, 441, 447, 454,
78    461, 467, 475, 482, 489, 497, 505, 513, 522, 530, 539, 549, 559, 569, 579, 590, 602, 614, 626,
79    640, 654, 668, 684, 700, 717, 736, 755, 775, 796, 819, 843, 869, 896, 925, 955, 988, 1022,
80    1058, 1098, 1139, 1184, 1232, 1282, 1336,
81];
82
83/// AC dequantizer lookup table for 8-bit depth.
84pub const AC_QLOOKUP: [i16; QINDEX_RANGE] = [
85    4, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
86    31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,
87    55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78,
88    79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101,
89    102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138,
90    140, 142, 144, 146, 148, 150, 152, 155, 158, 161, 164, 167, 170, 173, 176, 179, 182, 185, 188,
91    191, 194, 197, 200, 203, 207, 211, 215, 219, 223, 227, 231, 235, 239, 243, 247, 251, 255, 260,
92    265, 270, 275, 280, 285, 290, 295, 300, 305, 311, 317, 323, 329, 335, 341, 347, 353, 359, 366,
93    373, 380, 387, 394, 401, 408, 416, 424, 432, 440, 448, 456, 465, 474, 483, 492, 501, 510, 520,
94    530, 540, 550, 560, 571, 582, 593, 604, 615, 627, 639, 651, 663, 676, 689, 702, 715, 729, 743,
95    757, 771, 786, 801, 816, 832, 848, 864, 881, 898, 915, 933, 951, 969, 988, 1007, 1026, 1046,
96    1066, 1087, 1108, 1129, 1151, 1173, 1196, 1219, 1243, 1267, 1292, 1317, 1343, 1369, 1396, 1423,
97    1451, 1479, 1508, 1537, 1567, 1597, 1628, 1660, 1692, 1725, 1759, 1793, 1828,
98];
99
100/// DC dequantizer lookup table for 10-bit depth.
101pub const DC_QLOOKUP_10: [i16; QINDEX_RANGE] = [
102    4, 9, 10, 13, 15, 17, 20, 22, 25, 28, 31, 34, 37, 40, 43, 47, 50, 53, 57, 60, 64, 68, 71, 75,
103    78, 82, 86, 90, 93, 97, 101, 105, 109, 113, 116, 120, 124, 128, 132, 136, 140, 143, 147, 151,
104    155, 159, 163, 166, 170, 174, 178, 182, 185, 189, 193, 197, 200, 204, 208, 212, 215, 219, 223,
105    226, 230, 233, 237, 241, 244, 248, 251, 255, 259, 262, 266, 269, 273, 276, 280, 283, 287, 290,
106    293, 297, 300, 304, 307, 310, 314, 317, 321, 324, 327, 331, 334, 337, 343, 350, 356, 362, 369,
107    375, 381, 387, 394, 400, 406, 412, 418, 424, 430, 436, 442, 448, 454, 460, 466, 472, 478, 484,
108    490, 499, 507, 516, 525, 533, 542, 550, 559, 567, 576, 584, 592, 601, 609, 617, 625, 634, 644,
109    655, 666, 676, 687, 698, 708, 718, 729, 739, 749, 759, 770, 782, 795, 807, 819, 831, 844, 856,
110    868, 880, 891, 906, 920, 933, 947, 961, 975, 988, 1001, 1015, 1030, 1045, 1061, 1076, 1090,
111    1105, 1120, 1137, 1153, 1170, 1186, 1202, 1218, 1236, 1253, 1271, 1288, 1306, 1323, 1342, 1361,
112    1379, 1398, 1416, 1436, 1456, 1476, 1496, 1516, 1537, 1559, 1580, 1601, 1624, 1647, 1670, 1692,
113    1717, 1741, 1766, 1791, 1817, 1844, 1871, 1900, 1929, 1958, 1990, 2021, 2054, 2088, 2123, 2159,
114    2197, 2236, 2276, 2319, 2363, 2410, 2458, 2508, 2561, 2616, 2675, 2737, 2802, 2871, 2944, 3020,
115    3102, 3188, 3280, 3375, 3478, 3586, 3702, 3823, 3953, 4089, 4236, 4394, 4559, 4737, 4929, 5130,
116    5347,
117];
118
119/// AC dequantizer lookup table for 10-bit depth.
120pub const AC_QLOOKUP_10: [i16; QINDEX_RANGE] = [
121    4, 9, 11, 13, 16, 18, 21, 24, 27, 30, 33, 37, 40, 44, 48, 51, 55, 59, 63, 67, 71, 75, 79, 83,
122    88, 92, 96, 100, 105, 109, 114, 118, 122, 127, 131, 136, 140, 145, 149, 154, 158, 163, 168,
123    172, 177, 181, 186, 190, 195, 199, 204, 208, 213, 217, 222, 226, 231, 235, 240, 244, 249, 253,
124    258, 262, 267, 271, 275, 280, 284, 289, 293, 297, 302, 306, 311, 315, 319, 324, 328, 332, 337,
125    341, 345, 349, 354, 358, 362, 367, 371, 375, 379, 384, 388, 392, 396, 401, 409, 417, 425, 433,
126    441, 449, 458, 466, 474, 482, 490, 498, 506, 514, 523, 531, 539, 547, 555, 563, 571, 579, 588,
127    596, 604, 616, 628, 640, 652, 664, 676, 688, 700, 713, 725, 737, 749, 761, 773, 785, 797, 809,
128    825, 841, 857, 873, 889, 905, 922, 938, 954, 970, 986, 1002, 1018, 1038, 1058, 1078, 1098,
129    1118, 1138, 1158, 1178, 1198, 1218, 1242, 1266, 1290, 1314, 1338, 1362, 1386, 1411, 1435, 1463,
130    1491, 1519, 1547, 1575, 1603, 1631, 1663, 1695, 1727, 1759, 1791, 1823, 1859, 1895, 1931, 1967,
131    2003, 2039, 2079, 2119, 2159, 2199, 2239, 2283, 2327, 2371, 2415, 2459, 2507, 2555, 2603, 2651,
132    2703, 2755, 2807, 2859, 2915, 2971, 3027, 3083, 3143, 3203, 3263, 3327, 3391, 3455, 3523, 3591,
133    3659, 3731, 3803, 3876, 3952, 4028, 4104, 4184, 4264, 4348, 4432, 4516, 4604, 4692, 4784, 4876,
134    4972, 5068, 5168, 5268, 5372, 5476, 5584, 5692, 5804, 5916, 6032, 6148, 6268, 6388, 6512, 6640,
135    6768, 6900, 7036, 7172, 7312,
136];
137
138/// DC dequantizer lookup table for 12-bit depth.
139pub const DC_QLOOKUP_12: [i16; QINDEX_RANGE] = [
140    4, 12, 18, 25, 33, 41, 50, 60, 70, 80, 91, 103, 115, 127, 140, 153, 166, 180, 194, 208, 222,
141    237, 251, 266, 281, 296, 312, 327, 343, 358, 374, 390, 405, 421, 437, 453, 469, 484, 500, 516,
142    532, 548, 564, 580, 596, 611, 627, 643, 659, 674, 690, 706, 721, 737, 752, 768, 783, 798, 814,
143    829, 844, 859, 874, 889, 904, 919, 934, 949, 964, 978, 993, 1008, 1022, 1037, 1051, 1065, 1080,
144    1094, 1108, 1122, 1136, 1151, 1165, 1179, 1192, 1206, 1220, 1234, 1248, 1261, 1275, 1288, 1302,
145    1315, 1329, 1342, 1368, 1393, 1419, 1444, 1469, 1494, 1519, 1544, 1569, 1594, 1618, 1643, 1668,
146    1692, 1717, 1741, 1765, 1789, 1814, 1838, 1862, 1885, 1909, 1933, 1957, 1992, 2027, 2061, 2096,
147    2130, 2165, 2199, 2233, 2267, 2300, 2334, 2367, 2400, 2434, 2467, 2499, 2532, 2575, 2618, 2661,
148    2704, 2746, 2788, 2830, 2872, 2913, 2954, 2995, 3036, 3076, 3127, 3177, 3226, 3275, 3324, 3373,
149    3421, 3469, 3517, 3565, 3621, 3677, 3733, 3788, 3843, 3897, 3951, 4005, 4058, 4119, 4181, 4241,
150    4301, 4361, 4420, 4479, 4546, 4612, 4677, 4742, 4807, 4871, 4942, 5013, 5083, 5153, 5222, 5291,
151    5367, 5442, 5517, 5591, 5665, 5745, 5825, 5905, 5984, 6063, 6149, 6234, 6319, 6404, 6495, 6587,
152    6678, 6769, 6867, 6966, 7064, 7163, 7269, 7376, 7483, 7599, 7715, 7832, 7958, 8085, 8214, 8352,
153    8492, 8635, 8788, 8945, 9104, 9275, 9450, 9639, 9832, 10031, 10245, 10465, 10702, 10946, 11210,
154    11482, 11776, 12081, 12409, 12750, 13118, 13501, 13913, 14343, 14807, 15290, 15812, 16356,
155    16943, 17575, 18237, 18949, 19718, 20521, 21387,
156];
157
158/// AC dequantizer lookup table for 12-bit depth.
159pub const AC_QLOOKUP_12: [i16; QINDEX_RANGE] = [
160    4, 13, 19, 27, 35, 44, 54, 64, 75, 87, 99, 112, 126, 139, 154, 168, 183, 199, 214, 230, 247,
161    263, 280, 297, 314, 331, 349, 366, 384, 402, 420, 438, 456, 475, 493, 511, 530, 548, 567, 586,
162    604, 623, 642, 660, 679, 698, 716, 735, 753, 772, 791, 809, 828, 846, 865, 884, 902, 920, 939,
163    957, 976, 994, 1012, 1030, 1049, 1067, 1085, 1103, 1121, 1139, 1157, 1175, 1193, 1211, 1229,
164    1246, 1264, 1282, 1299, 1317, 1335, 1352, 1370, 1387, 1405, 1422, 1440, 1457, 1474, 1491, 1509,
165    1526, 1543, 1560, 1577, 1595, 1627, 1660, 1693, 1725, 1758, 1791, 1824, 1856, 1889, 1922, 1954,
166    1987, 2020, 2052, 2085, 2118, 2150, 2183, 2216, 2248, 2281, 2313, 2346, 2378, 2411, 2459, 2508,
167    2556, 2605, 2653, 2701, 2750, 2798, 2847, 2895, 2943, 2992, 3040, 3088, 3137, 3185, 3234, 3298,
168    3362, 3426, 3491, 3555, 3619, 3684, 3748, 3812, 3876, 3941, 4005, 4069, 4149, 4230, 4310, 4390,
169    4470, 4550, 4631, 4711, 4791, 4871, 4967, 5064, 5160, 5256, 5352, 5448, 5544, 5641, 5737, 5849,
170    5961, 6073, 6185, 6297, 6410, 6522, 6650, 6778, 6906, 7034, 7162, 7290, 7435, 7579, 7723, 7867,
171    8011, 8155, 8315, 8475, 8635, 8795, 8956, 9132, 9308, 9484, 9660, 9836, 10028, 10220, 10412,
172    10604, 10812, 11020, 11228, 11437, 11661, 11885, 12109, 12333, 12573, 12813, 13053, 13309,
173    13565, 13821, 14093, 14365, 14637, 14925, 15213, 15502, 15806, 16110, 16414, 16734, 17054,
174    17390, 17726, 18062, 18414, 18766, 19134, 19502, 19886, 20270, 20670, 21070, 21486, 21902,
175    22334, 22766, 23214, 23662, 24126, 24590, 25070, 25551, 26047, 26559, 27071, 27599, 28143,
176    28687, 29247,
177];
178
179// =============================================================================
180// Structures
181// =============================================================================
182
183/// Quantization parameters as parsed from the frame header.
184#[derive(Clone, Debug, Default)]
185pub struct QuantizationParams {
186    /// Base quantizer index (0-255).
187    pub base_q_idx: u8,
188    /// Delta Q for Y DC coefficients.
189    pub delta_q_y_dc: i8,
190    /// Delta Q for U DC coefficients.
191    pub delta_q_u_dc: i8,
192    /// Delta Q for U AC coefficients.
193    pub delta_q_u_ac: i8,
194    /// Delta Q for V DC coefficients.
195    pub delta_q_v_dc: i8,
196    /// Delta Q for V AC coefficients.
197    pub delta_q_v_ac: i8,
198    /// Use quantization matrices.
199    pub using_qmatrix: bool,
200    /// QM level for Y plane.
201    pub qm_y: u8,
202    /// QM level for U plane.
203    pub qm_u: u8,
204    /// QM level for V plane.
205    pub qm_v: u8,
206    /// Delta Q present in block level.
207    pub delta_q_present: bool,
208    /// Delta Q resolution (log2).
209    pub delta_q_res: u8,
210}
211
212impl QuantizationParams {
213    /// Create new quantization parameters with default values.
214    #[must_use]
215    pub fn new() -> Self {
216        Self::default()
217    }
218
219    /// Parse quantization parameters from the bitstream.
220    ///
221    /// # Errors
222    ///
223    /// Returns error if the bitstream is malformed.
224    #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
225    pub fn parse(reader: &mut BitReader<'_>, seq: &SequenceHeader) -> CodecResult<Self> {
226        let mut qp = Self::new();
227
228        // Base Q index
229        qp.base_q_idx = reader.read_bits(8).map_err(CodecError::Core)? as u8;
230
231        // Y DC delta
232        qp.delta_q_y_dc = Self::read_delta_q(reader)?;
233
234        // UV deltas
235        let num_planes = if seq.color_config.mono_chrome { 1 } else { 3 };
236
237        if num_planes > 1 {
238            let diff_uv_delta = if seq.color_config.separate_uv_delta_q {
239                reader.read_bit().map_err(CodecError::Core)? != 0
240            } else {
241                false
242            };
243
244            qp.delta_q_u_dc = Self::read_delta_q(reader)?;
245            qp.delta_q_u_ac = Self::read_delta_q(reader)?;
246
247            if diff_uv_delta {
248                qp.delta_q_v_dc = Self::read_delta_q(reader)?;
249                qp.delta_q_v_ac = Self::read_delta_q(reader)?;
250            } else {
251                qp.delta_q_v_dc = qp.delta_q_u_dc;
252                qp.delta_q_v_ac = qp.delta_q_u_ac;
253            }
254        }
255
256        // Quantization matrices
257        qp.using_qmatrix = reader.read_bit().map_err(CodecError::Core)? != 0;
258
259        if qp.using_qmatrix {
260            qp.qm_y = reader.read_bits(4).map_err(CodecError::Core)? as u8;
261            qp.qm_u = reader.read_bits(4).map_err(CodecError::Core)? as u8;
262
263            if seq.color_config.separate_uv_delta_q {
264                qp.qm_v = reader.read_bits(4).map_err(CodecError::Core)? as u8;
265            } else {
266                qp.qm_v = qp.qm_u;
267            }
268        }
269
270        Ok(qp)
271    }
272
273    /// Read a delta Q value from the bitstream.
274    #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
275    fn read_delta_q(reader: &mut BitReader<'_>) -> CodecResult<i8> {
276        let delta_coded = reader.read_bit().map_err(CodecError::Core)? != 0;
277
278        if delta_coded {
279            let abs_value = reader.read_bits(DELTA_Q_BITS).map_err(CodecError::Core)? as i8;
280            let sign = reader.read_bit().map_err(CodecError::Core)? != 0;
281            if sign {
282                Ok(-abs_value)
283            } else {
284                Ok(abs_value)
285            }
286        } else {
287            Ok(0)
288        }
289    }
290
291    /// Get the effective Q index for Y DC.
292    #[must_use]
293    pub fn y_dc_qindex(&self) -> u8 {
294        self.clamp_qindex(i16::from(self.base_q_idx) + i16::from(self.delta_q_y_dc))
295    }
296
297    /// Get the effective Q index for Y AC (same as base).
298    #[must_use]
299    pub const fn y_ac_qindex(&self) -> u8 {
300        self.base_q_idx
301    }
302
303    /// Get the effective Q index for U DC.
304    #[must_use]
305    pub fn u_dc_qindex(&self) -> u8 {
306        self.clamp_qindex(i16::from(self.base_q_idx) + i16::from(self.delta_q_u_dc))
307    }
308
309    /// Get the effective Q index for U AC.
310    #[must_use]
311    pub fn u_ac_qindex(&self) -> u8 {
312        self.clamp_qindex(i16::from(self.base_q_idx) + i16::from(self.delta_q_u_ac))
313    }
314
315    /// Get the effective Q index for V DC.
316    #[must_use]
317    pub fn v_dc_qindex(&self) -> u8 {
318        self.clamp_qindex(i16::from(self.base_q_idx) + i16::from(self.delta_q_v_dc))
319    }
320
321    /// Get the effective Q index for V AC.
322    #[must_use]
323    pub fn v_ac_qindex(&self) -> u8 {
324        self.clamp_qindex(i16::from(self.base_q_idx) + i16::from(self.delta_q_v_ac))
325    }
326
327    /// Clamp a Q index to valid range.
328    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
329    fn clamp_qindex(&self, q: i16) -> u8 {
330        q.clamp(i16::from(MIN_Q_INDEX), i16::from(MAX_Q_INDEX)) as u8
331    }
332
333    /// Get the DC dequantizer for Y plane.
334    #[must_use]
335    pub fn get_y_dc_dequant(&self, bit_depth: u8) -> i16 {
336        let qindex = self.y_dc_qindex();
337        get_dc_dequant(qindex, bit_depth)
338    }
339
340    /// Get the AC dequantizer for Y plane.
341    #[must_use]
342    pub fn get_y_ac_dequant(&self, bit_depth: u8) -> i16 {
343        let qindex = self.y_ac_qindex();
344        get_ac_dequant(qindex, bit_depth)
345    }
346
347    /// Get the DC dequantizer for U plane.
348    #[must_use]
349    pub fn get_u_dc_dequant(&self, bit_depth: u8) -> i16 {
350        let qindex = self.u_dc_qindex();
351        get_dc_dequant(qindex, bit_depth)
352    }
353
354    /// Get the AC dequantizer for U plane.
355    #[must_use]
356    pub fn get_u_ac_dequant(&self, bit_depth: u8) -> i16 {
357        let qindex = self.u_ac_qindex();
358        get_ac_dequant(qindex, bit_depth)
359    }
360
361    /// Get the DC dequantizer for V plane.
362    #[must_use]
363    pub fn get_v_dc_dequant(&self, bit_depth: u8) -> i16 {
364        let qindex = self.v_dc_qindex();
365        get_dc_dequant(qindex, bit_depth)
366    }
367
368    /// Get the AC dequantizer for V plane.
369    #[must_use]
370    pub fn get_v_ac_dequant(&self, bit_depth: u8) -> i16 {
371        let qindex = self.v_ac_qindex();
372        get_ac_dequant(qindex, bit_depth)
373    }
374
375    /// Check if lossless mode is enabled.
376    #[must_use]
377    pub fn is_lossless(&self) -> bool {
378        self.base_q_idx == 0
379            && self.delta_q_y_dc == 0
380            && self.delta_q_u_dc == 0
381            && self.delta_q_u_ac == 0
382            && self.delta_q_v_dc == 0
383            && self.delta_q_v_ac == 0
384    }
385
386    /// Check if any UV delta is non-zero.
387    #[must_use]
388    pub fn has_uv_delta(&self) -> bool {
389        self.delta_q_u_dc != 0
390            || self.delta_q_u_ac != 0
391            || self.delta_q_v_dc != 0
392            || self.delta_q_v_ac != 0
393    }
394
395    /// Get the QM level for a plane.
396    #[must_use]
397    pub const fn get_qm_level(&self, plane: usize) -> u8 {
398        match plane {
399            0 => self.qm_y,
400            1 => self.qm_u,
401            _ => self.qm_v,
402        }
403    }
404
405    /// Get DC quantizer for a plane (generic method).
406    #[must_use]
407    pub fn get_dc_quant(&self, plane: usize, bit_depth: u8) -> i16 {
408        match plane {
409            0 => self.get_y_dc_dequant(bit_depth),
410            1 => self.get_u_dc_dequant(bit_depth),
411            2 => self.get_v_dc_dequant(bit_depth),
412            _ => self.get_y_dc_dequant(bit_depth),
413        }
414    }
415
416    /// Get AC quantizer for a plane (generic method).
417    #[must_use]
418    pub fn get_ac_quant(&self, plane: usize, bit_depth: u8) -> i16 {
419        match plane {
420            0 => self.get_y_ac_dequant(bit_depth),
421            1 => self.get_u_ac_dequant(bit_depth),
422            2 => self.get_v_ac_dequant(bit_depth),
423            _ => self.get_y_ac_dequant(bit_depth),
424        }
425    }
426}
427
428/// Per-block delta Q state.
429#[derive(Clone, Debug, Default)]
430pub struct DeltaQState {
431    /// Current delta Q value.
432    pub delta_q: i16,
433    /// Resolution (1 << delta_q_res).
434    pub resolution: u8,
435}
436
437impl DeltaQState {
438    /// Create a new delta Q state.
439    #[must_use]
440    pub const fn new(resolution: u8) -> Self {
441        Self {
442            delta_q: 0,
443            resolution,
444        }
445    }
446
447    /// Apply delta Q to base Q index.
448    #[must_use]
449    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
450    pub fn apply(&self, base_q_idx: u8) -> u8 {
451        let q = i16::from(base_q_idx) + self.delta_q;
452        q.clamp(0, i16::from(MAX_Q_INDEX)) as u8
453    }
454
455    /// Update delta Q with a delta value.
456    #[allow(clippy::cast_possible_wrap)]
457    pub fn update(&mut self, delta: i16) {
458        self.delta_q += delta * ((1i16) << self.resolution);
459        self.delta_q = self
460            .delta_q
461            .clamp(i16::from(MIN_DELTA_Q), i16::from(MAX_DELTA_Q));
462    }
463
464    /// Reset delta Q to zero.
465    pub fn reset(&mut self) {
466        self.delta_q = 0;
467    }
468}
469
470// =============================================================================
471// Helper Functions
472// =============================================================================
473
474/// Get DC dequantizer value for a given Q index and bit depth.
475#[must_use]
476pub fn get_dc_dequant(qindex: u8, bit_depth: u8) -> i16 {
477    let table = match bit_depth {
478        10 => &DC_QLOOKUP_10,
479        12 => &DC_QLOOKUP_12,
480        _ => &DC_QLOOKUP,
481    };
482    table[qindex as usize]
483}
484
485/// Get AC dequantizer value for a given Q index and bit depth.
486#[must_use]
487pub fn get_ac_dequant(qindex: u8, bit_depth: u8) -> i16 {
488    let table = match bit_depth {
489        10 => &AC_QLOOKUP_10,
490        12 => &AC_QLOOKUP_12,
491        _ => &AC_QLOOKUP,
492    };
493    table[qindex as usize]
494}
495
496/// Convert Q index to quantizer value (for display/logging).
497#[must_use]
498pub fn qindex_to_qp(qindex: u8) -> f32 {
499    // Approximate conversion based on AV1 rate control models
500    let q = f32::from(qindex);
501    if q < 1.0 {
502        0.0
503    } else {
504        (q.log2() * 6.0) + 4.0
505    }
506}
507
508/// Convert QP value back to Q index (approximate).
509#[must_use]
510#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
511pub fn qp_to_qindex(qp: f32) -> u8 {
512    if qp < 4.0 {
513        0
514    } else {
515        let q = 2.0f32.powf((qp - 4.0) / 6.0);
516        (q.round() as u8).min(MAX_Q_INDEX)
517    }
518}
519
520// =============================================================================
521// Adaptive Quantization Matrix Selection by Content Type
522// =============================================================================
523
524/// Content type classification for adaptive QM level selection.
525#[derive(Clone, Copy, Debug, PartialEq, Eq)]
526pub enum QmContentType {
527    /// Flat/smooth regions — prefer low QM level.
528    Flat,
529    /// Natural texture — prefer medium QM level.
530    Texture,
531    /// High-frequency detail — prefer high QM level.
532    Detail,
533    /// Screen content — prefer very low QM level.
534    Screen,
535}
536
537/// Result of adaptive QM level selection.
538#[derive(Clone, Debug)]
539pub struct AdaptiveQmSelection {
540    /// Luma QM level 0-15, or `None` to disable.
541    pub qm_y: Option<u8>,
542    /// Cb QM level, or `None`.
543    pub qm_u: Option<u8>,
544    /// Cr QM level, or `None`.
545    pub qm_v: Option<u8>,
546    /// Content type used for selection.
547    pub content_type: QmContentType,
548    /// Base Q index used.
549    pub base_q_idx: u8,
550}
551
552/// Select adaptive QM levels based on content type and base Q index.
553///
554/// QM is disabled below Q=64 (high quality). Above that threshold the QM
555/// level scales with Q and is adjusted by content type.
556#[must_use]
557pub fn select_adaptive_qm(content_type: QmContentType, base_q_idx: u8) -> AdaptiveQmSelection {
558    if base_q_idx < 64 {
559        return AdaptiveQmSelection {
560            qm_y: None,
561            qm_u: None,
562            qm_v: None,
563            content_type,
564            base_q_idx,
565        };
566    }
567    let base_qm: u8 = match base_q_idx {
568        64..=127 => 4 + (u16::from(base_q_idx - 64) * 4 / 63) as u8,
569        128..=191 => 8 + (u16::from(base_q_idx - 128) * 4 / 63) as u8,
570        _ => (12 + (u16::from(base_q_idx - 192) * 3 / 63)).min(15) as u8,
571    };
572    let (y_adj, uv_adj): (i8, i8) = match content_type {
573        QmContentType::Flat => (-2, -1),
574        QmContentType::Texture => (0, 0),
575        QmContentType::Detail => (2, 1),
576        QmContentType::Screen => (-3, -2),
577    };
578    let c = |b: u8, a: i8| Some((i16::from(b) + i16::from(a)).clamp(0, 15) as u8);
579    AdaptiveQmSelection {
580        qm_y: c(base_qm, y_adj),
581        qm_u: c(base_qm, uv_adj),
582        qm_v: c(base_qm, uv_adj),
583        content_type,
584        base_q_idx,
585    }
586}
587
588/// Apply an `AdaptiveQmSelection` to `QuantizationParams`.
589pub fn apply_adaptive_qm(qp: &mut QuantizationParams, sel: &AdaptiveQmSelection) {
590    match (sel.qm_y, sel.qm_u, sel.qm_v) {
591        (None, None, None) => {
592            qp.using_qmatrix = false;
593            qp.qm_y = 0;
594            qp.qm_u = 0;
595            qp.qm_v = 0;
596        }
597        _ => {
598            qp.using_qmatrix = true;
599            qp.qm_y = sel.qm_y.unwrap_or(0);
600            qp.qm_u = sel.qm_u.unwrap_or(0);
601            qp.qm_v = sel.qm_v.unwrap_or(0);
602        }
603    }
604}
605
606// =============================================================================
607// Tests
608// =============================================================================
609
610#[cfg(test)]
611mod tests {
612    use super::*;
613
614    #[test]
615    fn test_quantization_params_default() {
616        let qp = QuantizationParams::default();
617        assert_eq!(qp.base_q_idx, 0);
618        assert_eq!(qp.delta_q_y_dc, 0);
619        assert!(!qp.using_qmatrix);
620    }
621
622    #[test]
623    fn test_qindex_accessors() {
624        let mut qp = QuantizationParams::default();
625        qp.base_q_idx = 100;
626        qp.delta_q_y_dc = -10;
627        qp.delta_q_u_dc = 5;
628        qp.delta_q_u_ac = -5;
629        qp.delta_q_v_dc = 10;
630        qp.delta_q_v_ac = -10;
631
632        assert_eq!(qp.y_dc_qindex(), 90);
633        assert_eq!(qp.y_ac_qindex(), 100);
634        assert_eq!(qp.u_dc_qindex(), 105);
635        assert_eq!(qp.u_ac_qindex(), 95);
636        assert_eq!(qp.v_dc_qindex(), 110);
637        assert_eq!(qp.v_ac_qindex(), 90);
638    }
639
640    #[test]
641    fn test_qindex_clamping() {
642        let mut qp = QuantizationParams::default();
643        qp.base_q_idx = 250;
644        qp.delta_q_y_dc = 20;
645
646        // Should clamp to 255
647        assert_eq!(qp.y_dc_qindex(), 255);
648
649        qp.base_q_idx = 10;
650        qp.delta_q_y_dc = -20;
651
652        // Should clamp to 0
653        assert_eq!(qp.y_dc_qindex(), 0);
654    }
655
656    #[test]
657    fn test_is_lossless() {
658        let mut qp = QuantizationParams::default();
659        assert!(qp.is_lossless());
660
661        qp.base_q_idx = 1;
662        assert!(!qp.is_lossless());
663
664        qp.base_q_idx = 0;
665        qp.delta_q_y_dc = 1;
666        assert!(!qp.is_lossless());
667    }
668
669    #[test]
670    fn test_has_uv_delta() {
671        let mut qp = QuantizationParams::default();
672        assert!(!qp.has_uv_delta());
673
674        qp.delta_q_u_dc = 5;
675        assert!(qp.has_uv_delta());
676
677        qp.delta_q_u_dc = 0;
678        qp.delta_q_v_ac = -3;
679        assert!(qp.has_uv_delta());
680    }
681
682    #[test]
683    fn test_get_qm_level() {
684        let mut qp = QuantizationParams::default();
685        qp.qm_y = 5;
686        qp.qm_u = 7;
687        qp.qm_v = 9;
688
689        assert_eq!(qp.get_qm_level(0), 5);
690        assert_eq!(qp.get_qm_level(1), 7);
691        assert_eq!(qp.get_qm_level(2), 9);
692    }
693
694    #[test]
695    fn test_dc_dequant_8bit() {
696        // Check some known values from the lookup table
697        assert_eq!(get_dc_dequant(0, 8), 4);
698        assert_eq!(get_dc_dequant(255, 8), 1336);
699    }
700
701    #[test]
702    fn test_ac_dequant_8bit() {
703        assert_eq!(get_ac_dequant(0, 8), 4);
704        assert_eq!(get_ac_dequant(255, 8), 1828);
705    }
706
707    #[test]
708    fn test_dc_dequant_10bit() {
709        assert_eq!(get_dc_dequant(0, 10), 4);
710        assert_eq!(get_dc_dequant(255, 10), 5347);
711    }
712
713    #[test]
714    fn test_ac_dequant_10bit() {
715        assert_eq!(get_ac_dequant(0, 10), 4);
716        assert_eq!(get_ac_dequant(255, 10), 7312);
717    }
718
719    #[test]
720    fn test_dc_dequant_12bit() {
721        assert_eq!(get_dc_dequant(0, 12), 4);
722        assert_eq!(get_dc_dequant(255, 12), 21387);
723    }
724
725    #[test]
726    fn test_ac_dequant_12bit() {
727        assert_eq!(get_ac_dequant(0, 12), 4);
728        assert_eq!(get_ac_dequant(255, 12), 29247);
729    }
730
731    #[test]
732    fn test_dequant_methods() {
733        let mut qp = QuantizationParams::default();
734        qp.base_q_idx = 128;
735
736        let y_dc = qp.get_y_dc_dequant(8);
737        let y_ac = qp.get_y_ac_dequant(8);
738
739        assert!(y_dc > 0);
740        assert!(y_ac > 0);
741
742        // DC values from the table at index 128
743        assert_eq!(y_dc, DC_QLOOKUP[128]);
744        assert_eq!(y_ac, AC_QLOOKUP[128]);
745    }
746
747    #[test]
748    fn test_delta_q_state() {
749        let mut state = DeltaQState::new(2);
750        assert_eq!(state.delta_q, 0);
751
752        state.update(5);
753        assert_eq!(state.delta_q, 20); // 5 * (1 << 2)
754
755        let q = state.apply(100);
756        assert_eq!(q, 120);
757
758        state.reset();
759        assert_eq!(state.delta_q, 0);
760    }
761
762    #[test]
763    fn test_delta_q_state_clamping() {
764        let mut state = DeltaQState::new(0);
765        state.delta_q = 50;
766
767        let q = state.apply(220);
768        assert_eq!(q, 255); // Clamped
769    }
770
771    #[test]
772    fn test_qindex_to_qp_conversion() {
773        // Q index 0 should give QP close to 0
774        let qp_0 = qindex_to_qp(0);
775        assert!(qp_0 < 1.0);
776
777        // Higher Q index should give higher QP
778        let qp_128 = qindex_to_qp(128);
779        let qp_64 = qindex_to_qp(64);
780        assert!(qp_128 > qp_64);
781    }
782
783    #[test]
784    fn test_qp_to_qindex_conversion() {
785        // Low QP should give low Q index
786        let q_low = qp_to_qindex(4.0);
787        assert!(q_low <= 10);
788
789        // High QP should give higher Q index
790        let q_high = qp_to_qindex(50.0);
791        assert!(q_high > q_low);
792    }
793
794    #[test]
795    fn test_constants() {
796        assert_eq!(MAX_Q_INDEX, 255);
797        assert_eq!(MIN_Q_INDEX, 0);
798        assert_eq!(QINDEX_RANGE, 256);
799        assert_eq!(NUM_QM_LEVELS, 16);
800    }
801
802    #[test]
803    fn test_lookup_table_lengths() {
804        assert_eq!(DC_QLOOKUP.len(), QINDEX_RANGE);
805        assert_eq!(AC_QLOOKUP.len(), QINDEX_RANGE);
806        assert_eq!(DC_QLOOKUP_10.len(), QINDEX_RANGE);
807        assert_eq!(AC_QLOOKUP_10.len(), QINDEX_RANGE);
808        assert_eq!(DC_QLOOKUP_12.len(), QINDEX_RANGE);
809        assert_eq!(AC_QLOOKUP_12.len(), QINDEX_RANGE);
810    }
811
812    #[test]
813    fn test_lookup_table_monotonic() {
814        // Verify tables are monotonically increasing
815        for i in 1..QINDEX_RANGE {
816            assert!(DC_QLOOKUP[i] >= DC_QLOOKUP[i - 1]);
817            assert!(AC_QLOOKUP[i] >= AC_QLOOKUP[i - 1]);
818        }
819    }
820
821    // =========================================================================
822    // Adaptive quantization matrix selection tests (Task 4)
823    // =========================================================================
824
825    #[test]
826    fn test_adaptive_qm_disabled_below_q64() {
827        for q in [0u8, 32, 63] {
828            let sel = select_adaptive_qm(QmContentType::Texture, q);
829            assert!(sel.qm_y.is_none(), "QM must be disabled for q_idx={q}");
830            assert!(sel.qm_u.is_none());
831            assert!(sel.qm_v.is_none());
832        }
833    }
834
835    #[test]
836    fn test_adaptive_qm_enabled_above_q64() {
837        for q in [64u8, 128, 191, 255] {
838            let sel = select_adaptive_qm(QmContentType::Texture, q);
839            assert!(sel.qm_y.is_some(), "QM must be enabled for q_idx={q}");
840        }
841    }
842
843    #[test]
844    fn test_adaptive_qm_flat_uses_lower_level_than_detail() {
845        let q = 128u8;
846        let flat = select_adaptive_qm(QmContentType::Flat, q);
847        let detail = select_adaptive_qm(QmContentType::Detail, q);
848        let flat_y = flat.qm_y.unwrap_or(0);
849        let detail_y = detail.qm_y.unwrap_or(0);
850        assert!(
851            flat_y < detail_y || flat_y == detail_y,
852            "Flat should use QM level <= Detail: flat={flat_y}, detail={detail_y}"
853        );
854    }
855
856    #[test]
857    fn test_adaptive_qm_screen_content_low_level() {
858        // Screen content should prefer the lowest QM level (sharpest matrix)
859        let q = 128u8;
860        let screen = select_adaptive_qm(QmContentType::Screen, q);
861        let texture = select_adaptive_qm(QmContentType::Texture, q);
862        let screen_y = screen.qm_y.unwrap_or(0);
863        let texture_y = texture.qm_y.unwrap_or(0);
864        assert!(
865            screen_y <= texture_y,
866            "Screen content must use QM level <= Texture: screen={screen_y}, texture={texture_y}"
867        );
868    }
869
870    #[test]
871    fn test_adaptive_qm_levels_in_valid_range() {
872        for q in [64u8, 96, 128, 160, 192, 220, 255] {
873            for ct in [
874                QmContentType::Flat,
875                QmContentType::Texture,
876                QmContentType::Detail,
877                QmContentType::Screen,
878            ] {
879                let sel = select_adaptive_qm(ct, q);
880                if let Some(y) = sel.qm_y {
881                    assert!(y <= 15, "qm_y={y} must be in [0,15]");
882                }
883                if let Some(u) = sel.qm_u {
884                    assert!(u <= 15, "qm_u={u} must be in [0,15]");
885                }
886                if let Some(v) = sel.qm_v {
887                    assert!(v <= 15, "qm_v={v} must be in [0,15]");
888                }
889            }
890        }
891    }
892
893    #[test]
894    fn test_apply_adaptive_qm_enables_matrix() {
895        let mut qp = QuantizationParams::default();
896        let sel = select_adaptive_qm(QmContentType::Texture, 128);
897        apply_adaptive_qm(&mut qp, &sel);
898        assert!(
899            qp.using_qmatrix,
900            "apply_adaptive_qm must set using_qmatrix=true for q>=64"
901        );
902    }
903
904    #[test]
905    fn test_apply_adaptive_qm_disables_for_low_q() {
906        let mut qp = QuantizationParams::default();
907        let sel = select_adaptive_qm(QmContentType::Flat, 32);
908        apply_adaptive_qm(&mut qp, &sel);
909        assert!(
910            !qp.using_qmatrix,
911            "apply_adaptive_qm must set using_qmatrix=false for q<64"
912        );
913    }
914
915    #[test]
916    fn test_adaptive_qm_content_type_preserved() {
917        let ct = QmContentType::Screen;
918        let sel = select_adaptive_qm(ct, 200);
919        assert_eq!(
920            sel.content_type, ct,
921            "Content type must be preserved in selection result"
922        );
923        assert_eq!(sel.base_q_idx, 200);
924    }
925
926    #[test]
927    fn test_adaptive_qm_monotone_with_q() {
928        // Higher Q index should produce equal or higher QM level
929        let ct = QmContentType::Texture;
930        let low_q = select_adaptive_qm(ct, 80);
931        let high_q = select_adaptive_qm(ct, 240);
932        let low_y = low_q.qm_y.unwrap_or(0);
933        let high_y = high_q.qm_y.unwrap_or(0);
934        assert!(
935            high_y >= low_y,
936            "Higher Q should give >= QM level: q=80 → {low_y}, q=240 → {high_y}"
937        );
938    }
939}