zvidlib_core/
codec_config.rs1use crate::{CodecProfile, Error, ErrorKind, Result};
6
7#[doc(hidden)]
9pub fn box_payload<'a>(whole: &'a [u8], expected_fourcc: &[u8; 4]) -> Result<&'a [u8]> {
10 if whole.len() < 8 {
11 return Err(Error::new(
12 ErrorKind::MalformedMedia,
13 "codec configuration box is too short",
14 ));
15 }
16 if &whole[4..8] != expected_fourcc {
17 return Err(Error::new(
18 ErrorKind::MalformedMedia,
19 "codec configuration box has an unexpected fourcc",
20 ));
21 }
22 Ok(&whole[8..])
23}
24
25#[derive(Clone, Copy, Debug, Eq, PartialEq)]
39pub struct Vp9CodecConfig {
40 pub profile: u8,
41 pub level: u8,
43 pub bit_depth: u8,
44 pub chroma_subsampling: u8,
47 pub video_full_range: bool,
48 pub color_primaries: u8,
50 pub transfer_characteristics: u8,
51 pub matrix_coefficients: u8,
52}
53
54impl Default for Vp9CodecConfig {
55 fn default() -> Self {
56 Self {
57 profile: 0,
58 level: 0,
59 bit_depth: 8,
60 chroma_subsampling: 0,
61 video_full_range: false,
62 color_primaries: 2,
63 transfer_characteristics: 2,
64 matrix_coefficients: 2,
65 }
66 }
67}
68
69const VP9_LEVELS: [u8; 14] = [10, 11, 20, 21, 30, 31, 40, 41, 50, 51, 52, 60, 61, 62];
70
71impl Vp9CodecConfig {
72 pub fn parse(bytes: &[u8]) -> Result<Self> {
76 if bytes.len() >= 8 && &bytes[4..8] == b"vpcC" {
77 Self::parse_vpcc(bytes)
78 } else {
79 Self::parse_webm_codec_private(bytes)
80 }
81 }
82
83 pub fn parse_vpcc(whole: &[u8]) -> Result<Self> {
85 let payload = box_payload(whole, b"vpcC")?;
86 let declared = u32::from_be_bytes(whole[..4].try_into().expect("4 bytes"));
87 if declared as usize != whole.len() {
88 return Err(malformed_config("vpcC box size is inconsistent"));
89 }
90 if payload.len() < 4 {
91 return Err(malformed_config("vpcC box is too short"));
92 }
93 let version = payload[0];
94 let record = &payload[4..];
95 let config = match version {
96 1 => {
97 if record.len() < 8 {
98 return Err(malformed_config("vpcC record is too short"));
99 }
100 Self {
101 profile: record[0],
102 level: record[1],
103 bit_depth: record[2] >> 4,
104 chroma_subsampling: (record[2] >> 1) & 0b111,
105 video_full_range: record[2] & 1 == 1,
106 color_primaries: record[3],
107 transfer_characteristics: record[4],
108 matrix_coefficients: record[5],
109 }
110 }
111 0 => {
112 if record.len() < 7 {
115 return Err(malformed_config("vpcC record is too short"));
116 }
117 Self {
118 profile: record[0],
119 level: record[1],
120 bit_depth: record[2] >> 4,
121 chroma_subsampling: record[3] >> 4,
122 video_full_range: record[4] >> 7 == 1,
123 ..Self::default()
124 }
125 }
126 _ => return Err(malformed_config("unsupported vpcC version")),
127 };
128 config.validate()
129 }
130
131 pub fn parse_webm_codec_private(bytes: &[u8]) -> Result<Self> {
133 let mut config = Self::default();
134 let mut rest = bytes;
135 while !rest.is_empty() {
136 if rest.len() < 2 {
137 return Err(malformed_config("VP9 CodecPrivate feature is truncated"));
138 }
139 let (id, length) = (rest[0], usize::from(rest[1]));
140 let value = rest
141 .get(2..2 + length)
142 .ok_or_else(|| malformed_config("VP9 CodecPrivate feature is truncated"))?;
143 if (1..=4).contains(&id) {
144 if length != 1 {
145 return Err(malformed_config(
146 "VP9 CodecPrivate feature has an invalid length",
147 ));
148 }
149 match id {
150 1 => config.profile = value[0],
151 2 => config.level = value[0],
152 3 => config.bit_depth = value[0],
153 _ => config.chroma_subsampling = value[0],
154 }
155 }
156 rest = &rest[2 + length..];
157 }
158 config.validate()
159 }
160
161 fn validate(self) -> Result<Self> {
162 if self.profile > 3 {
163 return Err(malformed_config("VP9 profile must be 0 to 3"));
164 }
165 if !matches!(self.bit_depth, 8 | 10 | 12) {
166 return Err(malformed_config("VP9 bit depth must be 8, 10 or 12"));
167 }
168 if self.chroma_subsampling > 3 {
169 return Err(malformed_config("VP9 chroma subsampling is invalid"));
170 }
171 Ok(self)
172 }
173
174 pub fn is_420(&self) -> bool {
176 self.chroma_subsampling <= 1
177 }
178
179 pub fn codec_profile(&self) -> Result<CodecProfile> {
181 Ok(match self.profile {
182 0 => CodecProfile::Vp9Profile0,
183 1 => CodecProfile::Vp9Profile1,
184 2 => CodecProfile::Vp9Profile2,
185 3 => CodecProfile::Vp9Profile3,
186 _ => return Err(malformed_config("VP9 profile must be 0 to 3")),
187 })
188 }
189
190 pub fn to_vpcc(&self) -> Vec<u8> {
193 let mut bytes = 20u32.to_be_bytes().to_vec();
194 bytes.extend_from_slice(b"vpcC");
195 bytes.extend_from_slice(&[1, 0, 0, 0, self.profile, self.level]);
196 bytes.push(
197 (self.bit_depth << 4)
198 | (self.chroma_subsampling << 1)
199 | u8::from(self.video_full_range),
200 );
201 bytes.extend_from_slice(&[
202 self.color_primaries,
203 self.transfer_characteristics,
204 self.matrix_coefficients,
205 0,
206 0,
207 ]);
208 bytes
209 }
210
211 pub fn codec_string(&self) -> String {
215 let level = if VP9_LEVELS.contains(&self.level) {
216 self.level
217 } else {
218 10
219 };
220 format!("vp09.{:02}.{level:02}.{:02}", self.profile, self.bit_depth)
221 }
222}
223
224fn malformed_config(message: &str) -> Error {
225 Error::new(ErrorKind::MalformedMedia, message)
226}