1mod import;
8mod split;
9
10pub use import::*;
11pub use split::*;
12
13use hang::catalog::AV1;
14
15#[derive(Debug, Clone, thiserror::Error)]
17#[non_exhaustive]
18pub enum Error {
19 #[error("OBU is too short")]
20 ObuTooShort,
21
22 #[error("OBU size too large")]
23 ObuSizeTooLarge,
24
25 #[error("not initialized")]
26 NotInitialized,
27
28 #[error("expected sequence header before any frames")]
29 MissingSequenceHeader,
30
31 #[error("missing timestamp")]
32 MissingTimestamp,
33
34 #[error("OBU header parse: {0}")]
35 ObuHeaderParse(std::sync::Arc<std::io::Error>),
36}
37
38impl From<std::io::Error> for Error {
39 fn from(err: std::io::Error) -> Self {
40 Error::ObuHeaderParse(std::sync::Arc::new(err))
41 }
42}
43
44pub type Result<T> = std::result::Result<T, Error>;
45
46pub(crate) fn config_from_av1c(av1c: &[u8]) -> Result<hang::catalog::VideoConfig> {
55 if av1c.len() < 4 || av1c[0] != 0x81 {
58 return Err(Error::ObuTooShort);
59 }
60 let high_bitdepth = ((av1c[2] >> 6) & 0x01) == 1;
61 let twelve_bit = ((av1c[2] >> 5) & 0x01) == 1;
62
63 let mut config = hang::catalog::VideoConfig::new(AV1 {
64 profile: (av1c[1] >> 5) & 0x07,
65 level: av1c[1] & 0x1f,
66 tier: if ((av1c[2] >> 7) & 0x01) == 1 { 'H' } else { 'M' },
67 bitdepth: bitdepth(twelve_bit, high_bitdepth),
68 mono_chrome: ((av1c[2] >> 4) & 0x01) == 1,
69 chroma_subsampling_x: ((av1c[2] >> 3) & 0x01) == 1,
70 chroma_subsampling_y: ((av1c[2] >> 2) & 0x01) == 1,
71 chroma_sample_position: av1c[2] & 0x03,
72 ..Default::default()
73 });
74 config.description = Some(bytes::Bytes::copy_from_slice(av1c));
75 config.container = hang::catalog::Container::Legacy;
76 Ok(config)
77}
78
79pub(crate) fn av1_from_av1c(av1c: &mp4_atom::Av1c) -> AV1 {
85 AV1 {
86 profile: av1c.seq_profile,
87 level: av1c.seq_level_idx_0,
88 bitdepth: bitdepth(av1c.twelve_bit, av1c.high_bitdepth),
89 mono_chrome: av1c.monochrome,
90 chroma_subsampling_x: av1c.chroma_subsampling_x,
91 chroma_subsampling_y: av1c.chroma_subsampling_y,
92 chroma_sample_position: av1c.chroma_sample_position,
93 ..Default::default()
94 }
95}
96
97pub(crate) fn av1c_from_av1(av1: &AV1) -> mp4_atom::Av1c {
107 let (twelve_bit, high_bitdepth) = bitdepth_flags(av1.bitdepth);
108 mp4_atom::Av1c {
109 seq_profile: av1.profile,
110 seq_level_idx_0: av1.level,
111 seq_tier_0: av1.tier == 'H',
112 high_bitdepth,
113 twelve_bit,
114 monochrome: av1.mono_chrome,
115 chroma_subsampling_x: av1.chroma_subsampling_x,
116 chroma_subsampling_y: av1.chroma_subsampling_y,
117 chroma_sample_position: av1.chroma_sample_position,
118 initial_presentation_delay: None,
119 config_obus: Vec::new(),
120 }
121}
122
123pub(crate) fn bitdepth(twelve_bit: bool, high_bitdepth: bool) -> u8 {
128 8 + 2 * u8::from(high_bitdepth) + 2 * u8::from(twelve_bit)
129}
130
131pub(crate) fn bitdepth_flags(bitdepth: u8) -> (bool, bool) {
135 (bitdepth >= 12, bitdepth >= 10)
136}
137
138#[cfg(test)]
139mod tests {
140 use super::{av1_from_av1c, av1c_from_av1, bitdepth, bitdepth_flags};
141 use hang::catalog::AV1;
142
143 #[test]
144 fn maps_bitdepth_flags() {
145 assert_eq!(bitdepth(false, false), 8);
146 assert_eq!(bitdepth(false, true), 10);
147 assert_eq!(bitdepth(true, true), 12);
148 assert_eq!(bitdepth(true, false), 10);
151 }
152
153 #[test]
154 fn bitdepth_flags_round_trip() {
155 for (depth, flags) in [(8, (false, false)), (10, (false, true)), (12, (true, true))] {
156 assert_eq!(bitdepth_flags(depth), flags);
157 let (twelve_bit, high_bitdepth) = flags;
158 assert_eq!(bitdepth(twelve_bit, high_bitdepth), depth);
159 }
160 }
161
162 #[test]
163 fn av1c_round_trips_catalog_fields() {
164 let av1 = AV1 {
165 profile: 0,
166 level: 8,
167 tier: 'H',
168 bitdepth: 10,
169 mono_chrome: false,
170 chroma_subsampling_x: true,
171 chroma_subsampling_y: true,
172 chroma_sample_position: 2,
173 ..Default::default()
175 };
176
177 let av1c = av1c_from_av1(&av1);
178 assert_eq!(av1c.seq_profile, 0);
179 assert_eq!(av1c.seq_level_idx_0, 8);
180 assert!(av1c.seq_tier_0);
181 assert!(av1c.high_bitdepth);
182 assert!(!av1c.twelve_bit);
183 assert!(av1c.chroma_subsampling_x);
184 assert!(av1c.chroma_subsampling_y);
185 assert_eq!(av1c.chroma_sample_position, 2);
186 assert!(av1c.config_obus.is_empty());
187
188 let back = av1_from_av1c(&av1c);
190 assert_eq!(back.profile, av1.profile);
191 assert_eq!(back.level, av1.level);
192 assert_eq!(back.bitdepth, av1.bitdepth);
193 assert_eq!(back.mono_chrome, av1.mono_chrome);
194 assert_eq!(back.chroma_subsampling_x, av1.chroma_subsampling_x);
195 assert_eq!(back.chroma_subsampling_y, av1.chroma_subsampling_y);
196 assert_eq!(back.chroma_sample_position, av1.chroma_sample_position);
197 }
198}