1use crate::codec::CodecProfile;
12use crate::media::Codec;
13use crate::{Av1CodecConfigurationRecord, Error, ErrorKind, Limits, Result};
14#[doc(inline)]
15pub use zvidlib_core::codec_config::Vp9CodecConfig;
16use zvidlib_core::codec_config::box_payload;
17
18#[derive(Clone, Debug, Eq, PartialEq)]
20pub struct DerivedCodecString {
21 pub codec_string: String,
22 pub profile: CodecProfile,
23}
24
25pub fn derive_codec_string(codec: Codec, decoder_config: &[u8]) -> Result<DerivedCodecString> {
31 match codec {
32 Codec::Hevc => derive_hevc(decoder_config),
33 Codec::Av1 => derive_av1(decoder_config),
34 Codec::Vp8 => Ok(DerivedCodecString {
37 codec_string: "vp8".to_owned(),
38 profile: CodecProfile::Vp8,
39 }),
40 Codec::Opus => {
44 crate::OpusHead::from_dops(decoder_config)?;
45 Ok(DerivedCodecString {
46 codec_string: "opus".into(),
47 profile: CodecProfile::Opus,
48 })
49 }
50 Codec::Vorbis => {
51 crate::VorbisConfig::from_codec_private(decoder_config)?;
52 Ok(DerivedCodecString {
53 codec_string: "vorbis".into(),
54 profile: CodecProfile::Vorbis,
55 })
56 }
57 Codec::Vp9 => {
58 let config = Vp9CodecConfig::parse(decoder_config)?;
59 Ok(DerivedCodecString {
60 codec_string: config.codec_string(),
61 profile: config.codec_profile()?,
62 })
63 }
64 Codec::UncompressedVideo | Codec::H264 | Codec::Aac => Err(Error::new(
65 ErrorKind::Unsupported,
66 "codec string derivation only supports HEVC, AV1, VP8, VP9, Opus and Vorbis",
67 )),
68 _ => Err(Error::new(
69 ErrorKind::Unsupported,
70 "codec string derivation only supports HEVC, AV1, VP8, VP9, Opus and Vorbis",
71 )),
72 }
73}
74
75fn derive_hevc(decoder_config: &[u8]) -> Result<DerivedCodecString> {
87 let payload = box_payload(decoder_config, b"hvcC")?;
88 if payload.len() < 13 {
89 return Err(Error::new(
90 ErrorKind::MalformedMedia,
91 "hvcC configuration is too short",
92 ));
93 }
94 let profile_space = (payload[1] >> 6) & 0b11;
95 let tier_flag = (payload[1] >> 5) & 0b1;
96 let profile_idc = payload[1] & 0b0001_1111;
97 let compatibility_flags = u32::from_be_bytes(payload[2..6].try_into().expect("4 bytes"));
98 let constraint_flags = &payload[6..12];
99 let level_idc = payload[12];
100
101 let profile_space_letter = match profile_space {
107 0 => String::new(),
108 1 => "A".to_owned(),
109 2 => "B".to_owned(),
110 3 => "C".to_owned(),
111 _ => unreachable!("profile_space is masked to 2 bits"),
112 };
113 let tier_letter = if tier_flag == 1 { 'H' } else { 'L' };
114
115 let mut constraint_hex = String::new();
116 let mut last_nonzero = None;
117 for (index, byte) in constraint_flags.iter().enumerate() {
118 if *byte != 0 {
119 last_nonzero = Some(index);
120 }
121 }
122 if let Some(last) = last_nonzero {
123 for byte in &constraint_flags[..=last] {
124 constraint_hex.push('.');
125 constraint_hex.push_str(&format!("{byte:02X}"));
126 }
127 }
128
129 let codec_string = format!(
130 "hev1.{profile_space_letter}{profile_idc}.{compatibility_flags:X}.{tier_letter}{level_idc}{constraint_hex}"
131 );
132
133 let profile = match profile_idc {
134 2 => CodecProfile::HevcMain10,
135 _ => CodecProfile::HevcMain,
136 };
137
138 Ok(DerivedCodecString {
139 codec_string,
140 profile,
141 })
142}
143
144fn derive_av1(decoder_config: &[u8]) -> Result<DerivedCodecString> {
161 let record = Av1CodecConfigurationRecord::parse(decoder_config, &Limits::default())?;
162 let seq_profile = record.seq_profile;
163 let seq_level_idx_0 = record.seq_level_idx_0;
164
165 let bit_depth: u32 = if record.high_bitdepth {
166 if seq_profile == 2 && record.twelve_bit {
167 12
168 } else {
169 10
170 }
171 } else {
172 8
173 };
174
175 let tier_letter = if record.seq_tier_0 { 'H' } else { 'M' };
176 let mono = u8::from(record.monochrome);
177 let chroma_subsampling_x = u8::from(record.chroma_subsampling_x);
178 let chroma_subsampling_y = u8::from(record.chroma_subsampling_y);
179 let chroma_sample_position = record.chroma_sample_position;
180 let codec_string = format!(
181 "av01.{seq_profile}.{seq_level_idx_0:02}{tier_letter}.{bit_depth:02}.{mono}.{chroma_subsampling_x}{chroma_subsampling_y}{chroma_sample_position}"
182 );
183
184 Ok(DerivedCodecString {
185 codec_string,
186 profile: match seq_profile {
187 0 => CodecProfile::Av1Main,
188 1 => CodecProfile::Av1High,
189 2 => CodecProfile::Av1Professional,
190 _ => unreachable!("validated AV1 profile"),
191 },
192 })
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 fn boxed(kind: &[u8; 4], payload: &[u8]) -> Vec<u8> {
200 let mut bytes = (8 + payload.len() as u32).to_be_bytes().to_vec();
201 bytes.extend_from_slice(kind);
202 bytes.extend_from_slice(payload);
203 bytes
204 }
205
206 #[test]
207 fn derives_hevc_main_profile_codec_string() {
208 let mut payload = vec![1_u8, 0b0000_0001];
210 payload.extend_from_slice(&0x6000_0000_u32.to_be_bytes()); payload.extend_from_slice(&[0, 0, 0, 0, 0, 0]); payload.push(93); payload.extend_from_slice(&[0; 12]); let hvcc = boxed(b"hvcC", &payload);
215
216 let derived = derive_codec_string(Codec::Hevc, &hvcc).unwrap();
217 assert_eq!(derived.codec_string, "hev1.1.60000000.L93");
218 assert_eq!(derived.profile, CodecProfile::HevcMain);
219 }
220
221 #[test]
222 fn derives_hevc_main10_profile_from_profile_idc_two() {
223 let mut payload = vec![1_u8, 0b0000_0010];
224 payload.extend_from_slice(&0x6000_0000_u32.to_be_bytes());
225 payload.extend_from_slice(&[0xB0, 0, 0, 0, 0, 0]);
226 payload.push(120);
227 payload.extend_from_slice(&[0; 12]);
228 let hvcc = boxed(b"hvcC", &payload);
229
230 let derived = derive_codec_string(Codec::Hevc, &hvcc).unwrap();
231 assert_eq!(derived.profile, CodecProfile::HevcMain10);
232 assert_eq!(derived.codec_string, "hev1.2.60000000.L120.B0");
233 }
234
235 #[test]
236 fn derives_av1_main_profile_codec_string() {
237 let av1c = boxed(b"av1C", &[0x81, 0x04, 0, 0]);
240 let derived = derive_codec_string(Codec::Av1, &av1c).unwrap();
241 assert_eq!(derived.profile, CodecProfile::Av1Main);
242 assert_eq!(derived.codec_string, "av01.0.04M.08.0.000");
243 }
244
245 #[test]
246 fn av1_codec_string_carries_every_chroma_sample_position() {
247 for pos in 0..4u8 {
250 let av1c = boxed(b"av1C", &[0x81, 0x04, 0x0c | pos, 0]);
251 let derived = derive_codec_string(Codec::Av1, &av1c).unwrap();
252 assert_eq!(derived.profile, CodecProfile::Av1Main);
253 assert_eq!(derived.codec_string, format!("av01.0.04M.08.0.11{pos}"));
254 }
255 }
256
257 #[test]
258 fn distinguishes_av1_high_and_professional_profiles() {
259 let high = boxed(b"av1C", &[0x81, 0x24, 0, 0]);
260 assert_eq!(
261 derive_codec_string(Codec::Av1, &high).unwrap().profile,
262 CodecProfile::Av1High
263 );
264 let professional = boxed(b"av1C", &[0x81, 0x44, 0x68, 0]);
265 assert_eq!(
266 derive_codec_string(Codec::Av1, &professional)
267 .unwrap()
268 .profile,
269 CodecProfile::Av1Professional
270 );
271 }
272
273 #[test]
274 fn rejects_wrong_fourcc() {
275 let bogus = boxed(b"esds", &[0, 0, 0, 0]);
276 let error = derive_codec_string(Codec::Hevc, &bogus).unwrap_err();
277 assert_eq!(error.kind(), ErrorKind::MalformedMedia);
278 }
279
280 #[test]
281 fn rejects_truncated_configuration() {
282 let short = boxed(b"hvcC", &[1, 2]);
283 let error = derive_codec_string(Codec::Hevc, &short).unwrap_err();
284 assert_eq!(error.kind(), ErrorKind::MalformedMedia);
285
286 let short_av1 = boxed(b"av1C", &[1]);
287 let error = derive_codec_string(Codec::Av1, &short_av1).unwrap_err();
288 assert_eq!(error.kind(), ErrorKind::MalformedMedia);
289 }
290
291 #[test]
292 fn derives_the_vp8_codec_string_without_a_configuration_record() {
293 let derived = derive_codec_string(Codec::Vp8, &[]).unwrap();
294 assert_eq!(derived.codec_string, "vp8");
295 assert_eq!(derived.profile, CodecProfile::Vp8);
296 }
297
298 #[test]
299 fn rejects_unsupported_codecs() {
300 let error = derive_codec_string(Codec::Aac, &boxed(b"esds", &[0; 4])).unwrap_err();
301 assert_eq!(error.kind(), ErrorKind::Unsupported);
302 let error =
303 derive_codec_string(Codec::UncompressedVideo, &boxed(b"hvcC", &[0; 13])).unwrap_err();
304 assert_eq!(error.kind(), ErrorKind::Unsupported);
305 }
306
307 fn vpcc(version: u8, record: &[u8]) -> Vec<u8> {
308 let mut payload = vec![version, 0, 0, 0];
309 payload.extend_from_slice(record);
310 boxed(b"vpcC", &payload)
311 }
312
313 #[test]
314 fn reads_a_version_1_vpcc_box_and_derives_its_codec_string() {
315 let bytes = vpcc(1, &[0, 31, 0x82, 1, 1, 1, 0, 0]);
318 let config = Vp9CodecConfig::parse(&bytes).unwrap();
319 assert_eq!(
320 config,
321 Vp9CodecConfig {
322 profile: 0,
323 level: 31,
324 bit_depth: 8,
325 chroma_subsampling: 1,
326 video_full_range: false,
327 color_primaries: 1,
328 transfer_characteristics: 1,
329 matrix_coefficients: 1,
330 }
331 );
332 assert!(config.is_420());
333 let derived = derive_codec_string(Codec::Vp9, &bytes).unwrap();
334 assert_eq!(derived.codec_string, "vp09.00.31.08");
335 assert_eq!(derived.profile, CodecProfile::Vp9Profile0);
336
337 let ten_bit = vpcc(1, &[2, 0, 0xa1, 9, 16, 9, 0, 0]);
338 let config = Vp9CodecConfig::parse(&ten_bit).unwrap();
339 assert!(config.video_full_range);
340 assert_eq!(config.codec_profile().unwrap(), CodecProfile::Vp9Profile2);
341 assert_eq!(config.codec_string(), "vp09.02.10.10");
343 }
344
345 #[test]
346 fn reads_a_version_0_vpcc_box() {
347 let bytes = vpcc(0, &[1, 20, 0x80, 0x30, 0x80, 0, 0]);
348 let config = Vp9CodecConfig::parse(&bytes).unwrap();
349 assert_eq!(config.profile, 1);
350 assert_eq!(config.level, 20);
351 assert_eq!(config.bit_depth, 8);
352 assert_eq!(config.chroma_subsampling, 3);
353 assert!(config.video_full_range);
354 }
355
356 #[test]
357 fn reads_webm_codec_private_features() {
358 assert_eq!(
359 Vp9CodecConfig::parse(&[]).unwrap(),
360 Vp9CodecConfig::default()
361 );
362 let config = Vp9CodecConfig::parse(&[1, 1, 0, 2, 1, 40, 3, 1, 8, 4, 1, 1]).unwrap();
363 assert_eq!((config.profile, config.level, config.bit_depth), (0, 40, 8));
364 assert_eq!(config.chroma_subsampling, 1);
365 assert_eq!(
367 Vp9CodecConfig::parse(&[9, 2, 7, 7, 1, 1, 2])
368 .unwrap()
369 .profile,
370 2
371 );
372 }
373
374 #[test]
375 fn rejects_malformed_vp9_configurations() {
376 for bytes in [
377 vec![1, 1],
378 vec![1, 2, 0, 0],
379 vec![3, 1, 9],
380 vpcc(1, &[0, 10]),
381 vpcc(2, &[0, 10, 0x80, 1, 1, 1, 0, 0]),
382 vpcc(1, &[4, 10, 0x80, 1, 1, 1, 0, 0]),
383 vpcc(1, &[0, 10, 0x90, 1, 1, 1, 0, 0]),
384 ] {
385 let error = Vp9CodecConfig::parse(&bytes).unwrap_err();
386 assert_eq!(error.kind(), ErrorKind::MalformedMedia, "{bytes:?}");
387 }
388 let config = Vp9CodecConfig::parse(&[1, 1, 0, 2, 1, 41, 4, 1, 1]).unwrap();
389 assert_eq!(Vp9CodecConfig::parse(&config.to_vpcc()).unwrap(), config);
390 let mut inconsistent = vpcc(1, &[0, 10, 0x80, 1, 1, 1, 0, 0]);
391 inconsistent[3] += 1;
392 assert!(Vp9CodecConfig::parse(&inconsistent).is_err());
393 }
394}