moq_vaapi/color.rs
1//! The YUV color space an encoded stream is labelled with.
2//!
3//! One value serves two jobs that have to agree: the encoder writes it into the
4//! SPS video usability information, and the post-processor uses it as the
5//! target of every conversion it runs for the encoder. Pixels the encoder
6//! converts therefore match their label. Pixels it takes as they are (a CPU
7//! upload, a YUV buffer with no color of its own) are labelled, not converted.
8
9use crate::bindings;
10
11/// The matrix that maps RGB to Y'CbCr.
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub enum Matrix {
14 /// ITU-R BT.601, the standard-definition matrix.
15 Bt601,
16 /// ITU-R BT.709, the high-definition matrix.
17 Bt709,
18}
19
20/// A YUV color space: the matrix and whether samples use the full 8-bit range.
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub struct Color {
23 /// The RGB to Y'CbCr matrix.
24 pub matrix: Matrix,
25 /// Whether luma spans 0..=255 rather than 16..=235.
26 pub full_range: bool,
27}
28
29impl Color {
30 /// BT.601 in limited range.
31 pub const BT601: Self = Self {
32 matrix: Matrix::Bt601,
33 full_range: false,
34 };
35 /// BT.709 in limited range.
36 pub const BT709: Self = Self {
37 matrix: Matrix::Bt709,
38 full_range: false,
39 };
40
41 /// Returns the conventional space for a stream of `height` lines.
42 ///
43 /// Standard definition (576 lines or fewer) is BT.601, anything taller
44 /// BT.709, both in limited range. That is the guess most players make for
45 /// an unlabelled stream, though not all of them (ffmpeg's scaler takes
46 /// BT.601 at every size), which is why the stream is labelled.
47 pub fn infer(height: u32) -> Self {
48 match height <= 576 {
49 true => Self::BT601,
50 false => Self::BT709,
51 }
52 }
53
54 /// Returns the H.264 VUI `(colour_primaries, transfer_characteristics, matrix_coefficients)`.
55 ///
56 /// BT.601 goes out as SMPTE 170M primaries and matrix (code point 6) with the
57 /// BT.709 transfer curve (1), since the two standards differ in primaries
58 /// and matrix but not in gamma.
59 pub(crate) fn vui(self) -> (u8, u8, u8) {
60 match self.matrix {
61 Matrix::Bt601 => (6, 1, 6),
62 Matrix::Bt709 => (1, 1, 1),
63 }
64 }
65
66 /// Returns the `VAProcColorStandardType` of the matrix.
67 pub(crate) fn va_standard(self) -> u8 {
68 (match self.matrix {
69 Matrix::Bt601 => bindings::_VAProcColorStandardType_VAProcColorStandardBT601,
70 Matrix::Bt709 => bindings::_VAProcColorStandardType_VAProcColorStandardBT709,
71 }) as u8
72 }
73
74 /// Returns the `VA_SOURCE_RANGE_*` of the range.
75 pub(crate) fn va_range(self) -> u8 {
76 (match self.full_range {
77 true => bindings::VA_SOURCE_RANGE_FULL,
78 false => bindings::VA_SOURCE_RANGE_REDUCED,
79 }) as u8
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 fn inference_splits_at_standard_definition() {
89 assert_eq!(Color::infer(480), Color::BT601);
90 assert_eq!(Color::infer(576), Color::BT601);
91 assert_eq!(Color::infer(720), Color::BT709);
92 }
93}