use crate::codec::CodecProfile;
use crate::media::Codec;
use crate::{Av1CodecConfigurationRecord, Error, ErrorKind, Limits, Result};
#[doc(inline)]
pub use zvidlib_core::codec_config::Vp9CodecConfig;
use zvidlib_core::codec_config::box_payload;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DerivedCodecString {
pub codec_string: String,
pub profile: CodecProfile,
}
pub fn derive_codec_string(codec: Codec, decoder_config: &[u8]) -> Result<DerivedCodecString> {
match codec {
Codec::Hevc => derive_hevc(decoder_config),
Codec::Av1 => derive_av1(decoder_config),
Codec::Vp8 => Ok(DerivedCodecString {
codec_string: "vp8".to_owned(),
profile: CodecProfile::Vp8,
}),
Codec::Opus => {
crate::OpusHead::from_dops(decoder_config)?;
Ok(DerivedCodecString {
codec_string: "opus".into(),
profile: CodecProfile::Opus,
})
}
Codec::Vorbis => {
crate::VorbisConfig::from_codec_private(decoder_config)?;
Ok(DerivedCodecString {
codec_string: "vorbis".into(),
profile: CodecProfile::Vorbis,
})
}
Codec::Vp9 => {
let config = Vp9CodecConfig::parse(decoder_config)?;
Ok(DerivedCodecString {
codec_string: config.codec_string(),
profile: config.codec_profile()?,
})
}
Codec::UncompressedVideo | Codec::H264 | Codec::Aac => Err(Error::new(
ErrorKind::Unsupported,
"codec string derivation only supports HEVC, AV1, VP8, VP9, Opus and Vorbis",
)),
_ => Err(Error::new(
ErrorKind::Unsupported,
"codec string derivation only supports HEVC, AV1, VP8, VP9, Opus and Vorbis",
)),
}
}
fn derive_hevc(decoder_config: &[u8]) -> Result<DerivedCodecString> {
let payload = box_payload(decoder_config, b"hvcC")?;
if payload.len() < 13 {
return Err(Error::new(
ErrorKind::MalformedMedia,
"hvcC configuration is too short",
));
}
let profile_space = (payload[1] >> 6) & 0b11;
let tier_flag = (payload[1] >> 5) & 0b1;
let profile_idc = payload[1] & 0b0001_1111;
let compatibility_flags = u32::from_be_bytes(payload[2..6].try_into().expect("4 bytes"));
let constraint_flags = &payload[6..12];
let level_idc = payload[12];
let profile_space_letter = match profile_space {
0 => String::new(),
1 => "A".to_owned(),
2 => "B".to_owned(),
3 => "C".to_owned(),
_ => unreachable!("profile_space is masked to 2 bits"),
};
let tier_letter = if tier_flag == 1 { 'H' } else { 'L' };
let mut constraint_hex = String::new();
let mut last_nonzero = None;
for (index, byte) in constraint_flags.iter().enumerate() {
if *byte != 0 {
last_nonzero = Some(index);
}
}
if let Some(last) = last_nonzero {
for byte in &constraint_flags[..=last] {
constraint_hex.push('.');
constraint_hex.push_str(&format!("{byte:02X}"));
}
}
let codec_string = format!(
"hev1.{profile_space_letter}{profile_idc}.{compatibility_flags:X}.{tier_letter}{level_idc}{constraint_hex}"
);
let profile = match profile_idc {
2 => CodecProfile::HevcMain10,
_ => CodecProfile::HevcMain,
};
Ok(DerivedCodecString {
codec_string,
profile,
})
}
fn derive_av1(decoder_config: &[u8]) -> Result<DerivedCodecString> {
let record = Av1CodecConfigurationRecord::parse(decoder_config, &Limits::default())?;
let seq_profile = record.seq_profile;
let seq_level_idx_0 = record.seq_level_idx_0;
let bit_depth: u32 = if record.high_bitdepth {
if seq_profile == 2 && record.twelve_bit {
12
} else {
10
}
} else {
8
};
let tier_letter = if record.seq_tier_0 { 'H' } else { 'M' };
let mono = u8::from(record.monochrome);
let chroma_subsampling_x = u8::from(record.chroma_subsampling_x);
let chroma_subsampling_y = u8::from(record.chroma_subsampling_y);
let chroma_sample_position = record.chroma_sample_position;
let codec_string = format!(
"av01.{seq_profile}.{seq_level_idx_0:02}{tier_letter}.{bit_depth:02}.{mono}.{chroma_subsampling_x}{chroma_subsampling_y}{chroma_sample_position}"
);
Ok(DerivedCodecString {
codec_string,
profile: match seq_profile {
0 => CodecProfile::Av1Main,
1 => CodecProfile::Av1High,
2 => CodecProfile::Av1Professional,
_ => unreachable!("validated AV1 profile"),
},
})
}
#[cfg(test)]
mod tests {
use super::*;
fn boxed(kind: &[u8; 4], payload: &[u8]) -> Vec<u8> {
let mut bytes = (8 + payload.len() as u32).to_be_bytes().to_vec();
bytes.extend_from_slice(kind);
bytes.extend_from_slice(payload);
bytes
}
#[test]
fn derives_hevc_main_profile_codec_string() {
let mut payload = vec![1_u8, 0b0000_0001];
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);
let derived = derive_codec_string(Codec::Hevc, &hvcc).unwrap();
assert_eq!(derived.codec_string, "hev1.1.60000000.L93");
assert_eq!(derived.profile, CodecProfile::HevcMain);
}
#[test]
fn derives_hevc_main10_profile_from_profile_idc_two() {
let mut payload = vec![1_u8, 0b0000_0010];
payload.extend_from_slice(&0x6000_0000_u32.to_be_bytes());
payload.extend_from_slice(&[0xB0, 0, 0, 0, 0, 0]);
payload.push(120);
payload.extend_from_slice(&[0; 12]);
let hvcc = boxed(b"hvcC", &payload);
let derived = derive_codec_string(Codec::Hevc, &hvcc).unwrap();
assert_eq!(derived.profile, CodecProfile::HevcMain10);
assert_eq!(derived.codec_string, "hev1.2.60000000.L120.B0");
}
#[test]
fn derives_av1_main_profile_codec_string() {
let av1c = boxed(b"av1C", &[0x81, 0x04, 0, 0]);
let derived = derive_codec_string(Codec::Av1, &av1c).unwrap();
assert_eq!(derived.profile, CodecProfile::Av1Main);
assert_eq!(derived.codec_string, "av01.0.04M.08.0.000");
}
#[test]
fn av1_codec_string_carries_every_chroma_sample_position() {
for pos in 0..4u8 {
let av1c = boxed(b"av1C", &[0x81, 0x04, 0x0c | pos, 0]);
let derived = derive_codec_string(Codec::Av1, &av1c).unwrap();
assert_eq!(derived.profile, CodecProfile::Av1Main);
assert_eq!(derived.codec_string, format!("av01.0.04M.08.0.11{pos}"));
}
}
#[test]
fn distinguishes_av1_high_and_professional_profiles() {
let high = boxed(b"av1C", &[0x81, 0x24, 0, 0]);
assert_eq!(
derive_codec_string(Codec::Av1, &high).unwrap().profile,
CodecProfile::Av1High
);
let professional = boxed(b"av1C", &[0x81, 0x44, 0x68, 0]);
assert_eq!(
derive_codec_string(Codec::Av1, &professional)
.unwrap()
.profile,
CodecProfile::Av1Professional
);
}
#[test]
fn rejects_wrong_fourcc() {
let bogus = boxed(b"esds", &[0, 0, 0, 0]);
let error = derive_codec_string(Codec::Hevc, &bogus).unwrap_err();
assert_eq!(error.kind(), ErrorKind::MalformedMedia);
}
#[test]
fn rejects_truncated_configuration() {
let short = boxed(b"hvcC", &[1, 2]);
let error = derive_codec_string(Codec::Hevc, &short).unwrap_err();
assert_eq!(error.kind(), ErrorKind::MalformedMedia);
let short_av1 = boxed(b"av1C", &[1]);
let error = derive_codec_string(Codec::Av1, &short_av1).unwrap_err();
assert_eq!(error.kind(), ErrorKind::MalformedMedia);
}
#[test]
fn derives_the_vp8_codec_string_without_a_configuration_record() {
let derived = derive_codec_string(Codec::Vp8, &[]).unwrap();
assert_eq!(derived.codec_string, "vp8");
assert_eq!(derived.profile, CodecProfile::Vp8);
}
#[test]
fn rejects_unsupported_codecs() {
let error = derive_codec_string(Codec::Aac, &boxed(b"esds", &[0; 4])).unwrap_err();
assert_eq!(error.kind(), ErrorKind::Unsupported);
let error =
derive_codec_string(Codec::UncompressedVideo, &boxed(b"hvcC", &[0; 13])).unwrap_err();
assert_eq!(error.kind(), ErrorKind::Unsupported);
}
fn vpcc(version: u8, record: &[u8]) -> Vec<u8> {
let mut payload = vec![version, 0, 0, 0];
payload.extend_from_slice(record);
boxed(b"vpcC", &payload)
}
#[test]
fn reads_a_version_1_vpcc_box_and_derives_its_codec_string() {
let bytes = vpcc(1, &[0, 31, 0x82, 1, 1, 1, 0, 0]);
let config = Vp9CodecConfig::parse(&bytes).unwrap();
assert_eq!(
config,
Vp9CodecConfig {
profile: 0,
level: 31,
bit_depth: 8,
chroma_subsampling: 1,
video_full_range: false,
color_primaries: 1,
transfer_characteristics: 1,
matrix_coefficients: 1,
}
);
assert!(config.is_420());
let derived = derive_codec_string(Codec::Vp9, &bytes).unwrap();
assert_eq!(derived.codec_string, "vp09.00.31.08");
assert_eq!(derived.profile, CodecProfile::Vp9Profile0);
let ten_bit = vpcc(1, &[2, 0, 0xa1, 9, 16, 9, 0, 0]);
let config = Vp9CodecConfig::parse(&ten_bit).unwrap();
assert!(config.video_full_range);
assert_eq!(config.codec_profile().unwrap(), CodecProfile::Vp9Profile2);
assert_eq!(config.codec_string(), "vp09.02.10.10");
}
#[test]
fn reads_a_version_0_vpcc_box() {
let bytes = vpcc(0, &[1, 20, 0x80, 0x30, 0x80, 0, 0]);
let config = Vp9CodecConfig::parse(&bytes).unwrap();
assert_eq!(config.profile, 1);
assert_eq!(config.level, 20);
assert_eq!(config.bit_depth, 8);
assert_eq!(config.chroma_subsampling, 3);
assert!(config.video_full_range);
}
#[test]
fn reads_webm_codec_private_features() {
assert_eq!(
Vp9CodecConfig::parse(&[]).unwrap(),
Vp9CodecConfig::default()
);
let config = Vp9CodecConfig::parse(&[1, 1, 0, 2, 1, 40, 3, 1, 8, 4, 1, 1]).unwrap();
assert_eq!((config.profile, config.level, config.bit_depth), (0, 40, 8));
assert_eq!(config.chroma_subsampling, 1);
assert_eq!(
Vp9CodecConfig::parse(&[9, 2, 7, 7, 1, 1, 2])
.unwrap()
.profile,
2
);
}
#[test]
fn rejects_malformed_vp9_configurations() {
for bytes in [
vec![1, 1],
vec![1, 2, 0, 0],
vec![3, 1, 9],
vpcc(1, &[0, 10]),
vpcc(2, &[0, 10, 0x80, 1, 1, 1, 0, 0]),
vpcc(1, &[4, 10, 0x80, 1, 1, 1, 0, 0]),
vpcc(1, &[0, 10, 0x90, 1, 1, 1, 0, 0]),
] {
let error = Vp9CodecConfig::parse(&bytes).unwrap_err();
assert_eq!(error.kind(), ErrorKind::MalformedMedia, "{bytes:?}");
}
let config = Vp9CodecConfig::parse(&[1, 1, 0, 2, 1, 41, 4, 1, 1]).unwrap();
assert_eq!(Vp9CodecConfig::parse(&config.to_vpcc()).unwrap(), config);
let mut inconsistent = vpcc(1, &[0, 10, 0x80, 1, 1, 1, 0, 0]);
inconsistent[3] += 1;
assert!(Vp9CodecConfig::parse(&inconsistent).is_err());
}
}