mod import;
mod split;
pub use import::*;
pub use split::*;
use hang::catalog::AV1;
#[derive(Debug, Clone, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("OBU is too short")]
ObuTooShort,
#[error("OBU size too large")]
ObuSizeTooLarge,
#[error("not initialized")]
NotInitialized,
#[error("expected sequence header before any frames")]
MissingSequenceHeader,
#[error("missing timestamp")]
MissingTimestamp,
#[error("OBU header parse: {0}")]
ObuHeaderParse(std::sync::Arc<std::io::Error>),
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Error::ObuHeaderParse(std::sync::Arc::new(err))
}
}
pub type Result<T> = std::result::Result<T, Error>;
pub(crate) fn config_from_av1c(av1c: &[u8]) -> Result<hang::catalog::VideoConfig> {
if av1c.len() < 4 || av1c[0] != 0x81 {
return Err(Error::ObuTooShort);
}
let high_bitdepth = ((av1c[2] >> 6) & 0x01) == 1;
let twelve_bit = ((av1c[2] >> 5) & 0x01) == 1;
let mut config = hang::catalog::VideoConfig::new(AV1 {
profile: (av1c[1] >> 5) & 0x07,
level: av1c[1] & 0x1f,
tier: if ((av1c[2] >> 7) & 0x01) == 1 { 'H' } else { 'M' },
bitdepth: bitdepth(twelve_bit, high_bitdepth),
mono_chrome: ((av1c[2] >> 4) & 0x01) == 1,
chroma_subsampling_x: ((av1c[2] >> 3) & 0x01) == 1,
chroma_subsampling_y: ((av1c[2] >> 2) & 0x01) == 1,
chroma_sample_position: av1c[2] & 0x03,
..Default::default()
});
config.description = Some(bytes::Bytes::copy_from_slice(av1c));
config.container = hang::catalog::Container::Legacy;
Ok(config)
}
pub(crate) fn av1_from_av1c(av1c: &mp4_atom::Av1c) -> AV1 {
AV1 {
profile: av1c.seq_profile,
level: av1c.seq_level_idx_0,
bitdepth: bitdepth(av1c.twelve_bit, av1c.high_bitdepth),
mono_chrome: av1c.monochrome,
chroma_subsampling_x: av1c.chroma_subsampling_x,
chroma_subsampling_y: av1c.chroma_subsampling_y,
chroma_sample_position: av1c.chroma_sample_position,
..Default::default()
}
}
pub(crate) fn av1c_from_av1(av1: &AV1) -> mp4_atom::Av1c {
let (twelve_bit, high_bitdepth) = bitdepth_flags(av1.bitdepth);
mp4_atom::Av1c {
seq_profile: av1.profile,
seq_level_idx_0: av1.level,
seq_tier_0: av1.tier == 'H',
high_bitdepth,
twelve_bit,
monochrome: av1.mono_chrome,
chroma_subsampling_x: av1.chroma_subsampling_x,
chroma_subsampling_y: av1.chroma_subsampling_y,
chroma_sample_position: av1.chroma_sample_position,
initial_presentation_delay: None,
config_obus: Vec::new(),
}
}
pub(crate) fn bitdepth(twelve_bit: bool, high_bitdepth: bool) -> u8 {
8 + 2 * u8::from(high_bitdepth) + 2 * u8::from(twelve_bit)
}
pub(crate) fn bitdepth_flags(bitdepth: u8) -> (bool, bool) {
(bitdepth >= 12, bitdepth >= 10)
}
#[cfg(test)]
mod tests {
use super::{av1_from_av1c, av1c_from_av1, bitdepth, bitdepth_flags};
use hang::catalog::AV1;
#[test]
fn maps_bitdepth_flags() {
assert_eq!(bitdepth(false, false), 8);
assert_eq!(bitdepth(false, true), 10);
assert_eq!(bitdepth(true, true), 12);
assert_eq!(bitdepth(true, false), 10);
}
#[test]
fn bitdepth_flags_round_trip() {
for (depth, flags) in [(8, (false, false)), (10, (false, true)), (12, (true, true))] {
assert_eq!(bitdepth_flags(depth), flags);
let (twelve_bit, high_bitdepth) = flags;
assert_eq!(bitdepth(twelve_bit, high_bitdepth), depth);
}
}
#[test]
fn av1c_round_trips_catalog_fields() {
let av1 = AV1 {
profile: 0,
level: 8,
tier: 'H',
bitdepth: 10,
mono_chrome: false,
chroma_subsampling_x: true,
chroma_subsampling_y: true,
chroma_sample_position: 2,
..Default::default()
};
let av1c = av1c_from_av1(&av1);
assert_eq!(av1c.seq_profile, 0);
assert_eq!(av1c.seq_level_idx_0, 8);
assert!(av1c.seq_tier_0);
assert!(av1c.high_bitdepth);
assert!(!av1c.twelve_bit);
assert!(av1c.chroma_subsampling_x);
assert!(av1c.chroma_subsampling_y);
assert_eq!(av1c.chroma_sample_position, 2);
assert!(av1c.config_obus.is_empty());
let back = av1_from_av1c(&av1c);
assert_eq!(back.profile, av1.profile);
assert_eq!(back.level, av1.level);
assert_eq!(back.bitdepth, av1.bitdepth);
assert_eq!(back.mono_chrome, av1.mono_chrome);
assert_eq!(back.chroma_subsampling_x, av1.chroma_subsampling_x);
assert_eq!(back.chroma_subsampling_y, av1.chroma_subsampling_y);
assert_eq!(back.chroma_sample_position, av1.chroma_sample_position);
}
}