Skip to main content

oximedia_codec/reconstruct/
mod.rs

1//! Video frame reconstruction pipeline.
2//!
3//! This module provides the complete reconstruction pipeline for decoded video frames,
4//! coordinating all stages from entropy decoding through final output formatting.
5//!
6//! # Pipeline Stages
7//!
8//! 1. **Parsing** - OBU/bitstream parsing
9//! 2. **Entropy** - Entropy decoding of coefficients
10//! 3. **Prediction** - Intra/inter prediction
11//! 4. **Transform** - Inverse transform of residuals
12//! 5. **Loop Filter** - Deblocking and edge filtering
13//! 6. **CDEF** - Constrained Directional Enhancement Filter
14//! 7. **Super-res** - AV1 super-resolution upscaling
15//! 8. **Film Grain** - Film grain synthesis
16//! 9. **Output** - Final format conversion
17//!
18//! # Example
19//!
20//! ```ignore
21//! use oximedia_codec::reconstruct::{DecoderPipeline, PipelineConfig};
22//!
23//! let config = PipelineConfig::default();
24//! let mut pipeline = DecoderPipeline::new(config)?;
25//!
26//! // Process a frame through all stages
27//! let output = pipeline.process_frame(&encoded_data)?;
28//! ```
29
30#![forbid(unsafe_code)]
31#![allow(clippy::unreadable_literal)]
32#![allow(clippy::items_after_statements)]
33#![allow(clippy::unnecessary_wraps)]
34#![allow(clippy::struct_excessive_bools)]
35#![allow(clippy::identity_op)]
36#![allow(clippy::range_plus_one)]
37#![allow(clippy::needless_range_loop)]
38#![allow(clippy::useless_conversion)]
39#![allow(clippy::redundant_closure_for_method_calls)]
40#![allow(clippy::single_match_else)]
41#![allow(dead_code)]
42#![allow(clippy::doc_markdown)]
43#![allow(clippy::module_name_repetitions)]
44#![allow(clippy::match_same_arms)]
45#![allow(clippy::similar_names)]
46#![allow(clippy::cast_possible_truncation)]
47#![allow(clippy::cast_precision_loss)]
48#![allow(clippy::cast_lossless)]
49#![allow(clippy::cast_sign_loss)]
50
51mod buffer;
52mod cdef_apply;
53mod deblock;
54mod film_grain;
55mod loop_filter;
56mod output;
57mod pipeline;
58mod residual;
59mod super_res;
60
61// Public exports
62pub use buffer::{BufferPool, FrameBuffer, PlaneBuffer, ReferenceFrameManager};
63pub use cdef_apply::{CdefApplicator, CdefBlockConfig, CdefFilterResult};
64pub use deblock::{DeblockFilter, DeblockParams, FilterStrength};
65pub use film_grain::{FilmGrainParams, FilmGrainSynthesizer, GrainBlock};
66pub use loop_filter::{EdgeFilter, FilterDirection, LoopFilterPipeline};
67pub use output::{OutputConfig, OutputFormat, OutputFormatter};
68pub use pipeline::{DecoderPipeline, FrameContext, PipelineConfig, PipelineStage, StageResult};
69pub use residual::{ResidualBuffer, ResidualPlane};
70pub use super_res::{SuperResConfig, SuperResUpscaler, UpscaleMethod};
71
72use thiserror::Error;
73
74// =============================================================================
75// Error Types
76// =============================================================================
77
78/// Errors that can occur during frame reconstruction.
79#[derive(Debug, Error)]
80pub enum ReconstructionError {
81    /// Invalid input data.
82    #[error("Invalid input: {0}")]
83    InvalidInput(String),
84
85    /// Buffer allocation failed.
86    #[error("Buffer allocation failed: {0}")]
87    AllocationFailed(String),
88
89    /// Reference frame not available.
90    #[error("Reference frame not available: index {0}")]
91    ReferenceNotAvailable(usize),
92
93    /// Pipeline stage error.
94    #[error("Pipeline stage '{stage}' failed: {message}")]
95    StageError {
96        /// The stage that failed.
97        stage: String,
98        /// Error message.
99        message: String,
100    },
101
102    /// Invalid dimensions.
103    #[error("Invalid dimensions: {width}x{height}")]
104    InvalidDimensions {
105        /// Width.
106        width: u32,
107        /// Height.
108        height: u32,
109    },
110
111    /// Unsupported bit depth.
112    #[error("Unsupported bit depth: {0}")]
113    UnsupportedBitDepth(u8),
114
115    /// Coefficient overflow.
116    #[error("Coefficient overflow at ({x}, {y})")]
117    CoefficientOverflow {
118        /// X coordinate.
119        x: usize,
120        /// Y coordinate.
121        y: usize,
122    },
123
124    /// Filter parameter out of range.
125    #[error("Filter parameter out of range: {name} = {value}")]
126    FilterParameterOutOfRange {
127        /// Parameter name.
128        name: String,
129        /// Parameter value.
130        value: i32,
131    },
132
133    /// Internal error.
134    #[error("Internal error: {0}")]
135    Internal(String),
136}
137
138/// Result type for reconstruction operations.
139pub type ReconstructResult<T> = Result<T, ReconstructionError>;
140
141// =============================================================================
142// Common Constants
143// =============================================================================
144
145/// Maximum supported bit depth.
146pub const MAX_BIT_DEPTH: u8 = 12;
147
148/// Minimum supported bit depth.
149pub const MIN_BIT_DEPTH: u8 = 8;
150
151/// Maximum frame width.
152pub const MAX_FRAME_WIDTH: u32 = 16384;
153
154/// Maximum frame height.
155pub const MAX_FRAME_HEIGHT: u32 = 16384;
156
157/// Number of reference frame slots.
158pub const NUM_REF_FRAMES: usize = 8;
159
160/// Maximum superblock size.
161pub const MAX_SB_SIZE: usize = 128;
162
163/// Minimum superblock size.
164pub const MIN_SB_SIZE: usize = 64;
165
166// =============================================================================
167// Common Types
168// =============================================================================
169
170/// Plane identifier.
171#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
172pub enum PlaneType {
173    /// Luma plane.
174    Y,
175    /// Chroma U plane.
176    U,
177    /// Chroma V plane.
178    V,
179}
180
181impl PlaneType {
182    /// Get plane index (0, 1, or 2).
183    #[must_use]
184    pub const fn index(self) -> usize {
185        match self {
186            Self::Y => 0,
187            Self::U => 1,
188            Self::V => 2,
189        }
190    }
191
192    /// Check if this is a chroma plane.
193    #[must_use]
194    pub const fn is_chroma(self) -> bool {
195        !matches!(self, Self::Y)
196    }
197
198    /// Get all plane types.
199    #[must_use]
200    pub const fn all() -> [Self; 3] {
201        [Self::Y, Self::U, Self::V]
202    }
203}
204
205/// Chroma subsampling format.
206#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
207pub enum ChromaSubsampling {
208    /// 4:4:4 - No subsampling.
209    Cs444,
210    /// 4:2:2 - Horizontal subsampling.
211    Cs422,
212    /// 4:2:0 - Horizontal and vertical subsampling.
213    #[default]
214    Cs420,
215    /// Monochrome.
216    Mono,
217}
218
219impl ChromaSubsampling {
220    /// Get subsampling ratios (horizontal, vertical).
221    #[must_use]
222    pub const fn ratios(self) -> (u32, u32) {
223        match self {
224            Self::Cs444 => (1, 1),
225            Self::Cs422 => (2, 1),
226            Self::Cs420 => (2, 2),
227            Self::Mono => (1, 1),
228        }
229    }
230
231    /// Get number of planes.
232    #[must_use]
233    pub const fn num_planes(self) -> usize {
234        match self {
235            Self::Mono => 1,
236            _ => 3,
237        }
238    }
239
240    /// Calculate chroma dimensions from luma dimensions.
241    #[must_use]
242    pub fn chroma_size(self, luma_width: u32, luma_height: u32) -> (u32, u32) {
243        let (h_ratio, v_ratio) = self.ratios();
244        (luma_width.div_ceil(h_ratio), luma_height.div_ceil(v_ratio))
245    }
246}
247
248#[cfg(test)]
249mod tests {
250    use super::*;
251
252    #[test]
253    fn test_plane_type() {
254        assert_eq!(PlaneType::Y.index(), 0);
255        assert_eq!(PlaneType::U.index(), 1);
256        assert_eq!(PlaneType::V.index(), 2);
257
258        assert!(!PlaneType::Y.is_chroma());
259        assert!(PlaneType::U.is_chroma());
260        assert!(PlaneType::V.is_chroma());
261    }
262
263    #[test]
264    fn test_chroma_subsampling() {
265        assert_eq!(ChromaSubsampling::Cs444.ratios(), (1, 1));
266        assert_eq!(ChromaSubsampling::Cs422.ratios(), (2, 1));
267        assert_eq!(ChromaSubsampling::Cs420.ratios(), (2, 2));
268
269        assert_eq!(ChromaSubsampling::Cs420.num_planes(), 3);
270        assert_eq!(ChromaSubsampling::Mono.num_planes(), 1);
271    }
272
273    #[test]
274    fn test_chroma_size_calculation() {
275        let cs = ChromaSubsampling::Cs420;
276        assert_eq!(cs.chroma_size(1920, 1080), (960, 540));
277        assert_eq!(cs.chroma_size(1921, 1081), (961, 541));
278    }
279
280    #[test]
281    fn test_reconstruction_error_display() {
282        let err = ReconstructionError::InvalidInput("test".to_string());
283        assert_eq!(format!("{err}"), "Invalid input: test");
284
285        let err = ReconstructionError::ReferenceNotAvailable(3);
286        assert_eq!(format!("{err}"), "Reference frame not available: index 3");
287
288        let err = ReconstructionError::InvalidDimensions {
289            width: 0,
290            height: 100,
291        };
292        assert_eq!(format!("{err}"), "Invalid dimensions: 0x100");
293    }
294
295    #[test]
296    fn test_constants() {
297        assert_eq!(MAX_BIT_DEPTH, 12);
298        assert_eq!(MIN_BIT_DEPTH, 8);
299        assert_eq!(NUM_REF_FRAMES, 8);
300        assert_eq!(MAX_SB_SIZE, 128);
301        assert_eq!(MIN_SB_SIZE, 64);
302    }
303}