Skip to main content

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}