av_metrics/video/
decode.rs

1//! Contains a trait and utilities for implementing decoders.
2//! Prebuilt decoders are included in the `av-metrics-decoders` crate.
3
4use crate::video::pixel::Pixel;
5use crate::video::{ChromaSamplePosition, ChromaSampling};
6use std::cmp;
7use v_frame::frame::Frame;
8use v_frame::pixel::CastFromPrimitive;
9use v_frame::plane::Plane;
10
11/// A trait for allowing metrics to decode generic video formats.
12///
13/// Currently, y4m decoding support using the `y4m` crate is built-in
14/// to this crate. This trait is extensible so users may implement
15/// their own decoders.
16pub trait Decoder: Send {
17    /// Read the next frame from the input video.
18    ///
19    /// Expected to return `Err` if the end of the video is reached.
20    fn read_video_frame<T: Pixel>(&mut self) -> Option<Frame<T>>;
21    /// Read a specific frame from the input video
22    ///
23    /// Expected to return `Err` if the frame is not found.
24    fn read_specific_frame<T: Pixel>(&mut self, frame_number: usize) -> Option<Frame<T>> {
25        let mut frame_no = 0;
26        while frame_no <= frame_number {
27            let frame = self.read_video_frame();
28            if frame_no == frame_number && frame.is_some() {
29                return frame;
30            }
31            frame_no += 1;
32        }
33        None
34    }
35    /// Get the bit depth of the video.
36    fn get_bit_depth(&self) -> usize;
37    /// Get the Video Details
38    fn get_video_details(&self) -> VideoDetails;
39}
40
41/// A Structure containing Video Details as per Plane's Config
42#[derive(Debug, Clone, Copy)]
43pub struct VideoDetails {
44    /// Width in pixels.
45    pub width: usize,
46    /// Height in pixels.
47    pub height: usize,
48    /// Bit-depth of the Video
49    pub bit_depth: usize,
50    /// ChromaSampling of the Video.
51    pub chroma_sampling: ChromaSampling,
52    /// Chroma Sampling Position of the Video.
53    pub chroma_sample_position: ChromaSamplePosition,
54    /// Add Time base of the Video.
55    pub time_base: Rational,
56    /// Padding Constant
57    pub luma_padding: usize,
58}
59
60impl Default for VideoDetails {
61    fn default() -> Self {
62        VideoDetails {
63            width: 640,
64            height: 480,
65            bit_depth: 8,
66            chroma_sampling: ChromaSampling::Cs420,
67            chroma_sample_position: ChromaSamplePosition::Unknown,
68            time_base: Rational { num: 30, den: 1 },
69            luma_padding: 0,
70        }
71    }
72}
73
74/// A rational number.
75#[derive(Clone, Copy, Debug)]
76#[repr(C)]
77pub struct Rational {
78    /// Numerator.
79    pub num: u64,
80    /// Denominator.
81    pub den: u64,
82}
83
84impl Rational {
85    /// Creates a rational number from the given numerator and denominator.
86    pub const fn new(num: u64, den: u64) -> Self {
87        Rational { num, den }
88    }
89    /// Returns a rational number that is the reciprocal of the given one.
90    pub const fn from_reciprocal(reciprocal: Self) -> Self {
91        Rational {
92            num: reciprocal.den,
93            den: reciprocal.num,
94        }
95    }
96    /// Returns the rational number as a floating-point number.
97    pub fn as_f64(self) -> f64 {
98        self.num as f64 / self.den as f64
99    }
100}
101
102/// The algorithms (as ported from daala-tools) expect a colocated or bilaterally located chroma
103/// sample position. This means that a vertical chroma sample position must be realigned
104/// in order to produce a correct result.
105pub fn convert_chroma_data<T: Pixel>(
106    plane_data: &mut Plane<T>,
107    chroma_pos: ChromaSamplePosition,
108    bit_depth: usize,
109    source: &[u8],
110    source_stride: usize,
111    source_bytewidth: usize,
112) {
113    if chroma_pos != ChromaSamplePosition::Vertical {
114        // TODO: Also convert Interpolated chromas
115        plane_data.copy_from_raw_u8(source, source_stride, source_bytewidth);
116        return;
117    }
118
119    let get_pixel = if source_bytewidth == 1 {
120        fn convert_u8(line: &[u8], index: usize) -> i32 {
121            i32::cast_from(line[index])
122        }
123        convert_u8
124    } else {
125        fn convert_u16(line: &[u8], index: usize) -> i32 {
126            let index = index * 2;
127            i32::cast_from(u16::cast_from(line[index + 1]) << 8 | u16::cast_from(line[index]))
128        }
129        convert_u16
130    };
131
132    let output_data = &mut plane_data.data;
133    let width = plane_data.cfg.width;
134    let height = plane_data.cfg.height;
135    for y in 0..height {
136        // Filter: [4 -17 114 35 -9 1]/128, derived from a 6-tap Lanczos window.
137        let in_row = &source[(y * source_stride)..];
138        let out_row = &mut output_data[(y * width)..];
139        let breakpoint = cmp::min(width, 2);
140        for x in 0..breakpoint {
141            out_row[x] = T::cast_from(clamp(
142                (4 * get_pixel(in_row, 0) - 17 * get_pixel(in_row, x.saturating_sub(1))
143                    + 114 * get_pixel(in_row, x)
144                    + 35 * get_pixel(in_row, cmp::min(x + 1, width - 1))
145                    - 9 * get_pixel(in_row, cmp::min(x + 2, width - 1))
146                    + get_pixel(in_row, cmp::min(x + 3, width - 1))
147                    + 64)
148                    >> 7,
149                0,
150                (1 << bit_depth) - 1,
151            ));
152        }
153        let breakpoint2 = width - 3;
154        for x in breakpoint..breakpoint2 {
155            out_row[x] = T::cast_from(clamp(
156                (4 * get_pixel(in_row, x - 2) - 17 * get_pixel(in_row, x - 1)
157                    + 114 * get_pixel(in_row, x)
158                    + 35 * get_pixel(in_row, x + 1)
159                    - 9 * get_pixel(in_row, x + 2)
160                    + get_pixel(in_row, x + 3)
161                    + 64)
162                    >> 7,
163                0,
164                (1 << bit_depth) - 1,
165            ));
166        }
167        for x in breakpoint2..width {
168            out_row[x] = T::cast_from(clamp(
169                (4 * get_pixel(in_row, x - 2) - 17 * get_pixel(in_row, x - 1)
170                    + 114 * get_pixel(in_row, x)
171                    + 35 * get_pixel(in_row, cmp::min(x + 1, width - 1))
172                    - 9 * get_pixel(in_row, cmp::min(x + 2, width - 1))
173                    + get_pixel(in_row, width - 1)
174                    + 64)
175                    >> 7,
176                0,
177                (1 << bit_depth) - 1,
178            ));
179        }
180    }
181}
182
183#[inline]
184fn clamp<T: PartialOrd>(input: T, min: T, max: T) -> T {
185    if input < min {
186        min
187    } else if input > max {
188        max
189    } else {
190        input
191    }
192}