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 Ok(config)
76}
77
78pub(crate) fn av1_from_av1c(av1c: &mp4_atom::Av1c) -> AV1 {
84 AV1 {
85 profile: av1c.seq_profile,
86 level: av1c.seq_level_idx_0,
87 bitdepth: bitdepth(av1c.twelve_bit, av1c.high_bitdepth),
88 mono_chrome: av1c.monochrome,
89 chroma_subsampling_x: av1c.chroma_subsampling_x,
90 chroma_subsampling_y: av1c.chroma_subsampling_y,
91 chroma_sample_position: av1c.chroma_sample_position,
92 ..Default::default()
93 }
94}
95
96pub(crate) fn av1c_from_av1(av1: &AV1) -> mp4_atom::Av1c {
106 let (twelve_bit, high_bitdepth) = bitdepth_flags(av1.bitdepth);
107 mp4_atom::Av1c {
108 seq_profile: av1.profile,
109 seq_level_idx_0: av1.level,
110 seq_tier_0: av1.tier == 'H',
111 high_bitdepth,
112 twelve_bit,
113 monochrome: av1.mono_chrome,
114 chroma_subsampling_x: av1.chroma_subsampling_x,
115 chroma_subsampling_y: av1.chroma_subsampling_y,
116 chroma_sample_position: av1.chroma_sample_position,
117 initial_presentation_delay: None,
118 config_obus: Vec::new(),
119 }
120}
121
122pub(crate) fn bitdepth(twelve_bit: bool, high_bitdepth: bool) -> u8 {
127 8 + 2 * u8::from(high_bitdepth) + 2 * u8::from(twelve_bit)
128}
129
130pub(crate) fn bitdepth_flags(bitdepth: u8) -> (bool, bool) {
134 (bitdepth >= 12, bitdepth >= 10)
135}
136
137#[cfg(test)]
138mod tests {
139 use super::{av1_from_av1c, av1c_from_av1, bitdepth, bitdepth_flags};
140 use hang::catalog::AV1;
141
142 #[test]
143 fn maps_bitdepth_flags() {
144 assert_eq!(bitdepth(false, false), 8);
145 assert_eq!(bitdepth(false, true), 10);
146 assert_eq!(bitdepth(true, true), 12);
147 assert_eq!(bitdepth(true, false), 10);
150 }
151
152 #[test]
153 fn bitdepth_flags_round_trip() {
154 for (depth, flags) in [(8, (false, false)), (10, (false, true)), (12, (true, true))] {
155 assert_eq!(bitdepth_flags(depth), flags);
156 let (twelve_bit, high_bitdepth) = flags;
157 assert_eq!(bitdepth(twelve_bit, high_bitdepth), depth);
158 }
159 }
160
161 #[test]
162 fn av1c_round_trips_catalog_fields() {
163 let av1 = AV1 {
164 profile: 0,
165 level: 8,
166 tier: 'H',
167 bitdepth: 10,
168 mono_chrome: false,
169 chroma_subsampling_x: true,
170 chroma_subsampling_y: true,
171 chroma_sample_position: 2,
172 ..Default::default()
174 };
175
176 let av1c = av1c_from_av1(&av1);
177 assert_eq!(av1c.seq_profile, 0);
178 assert_eq!(av1c.seq_level_idx_0, 8);
179 assert!(av1c.seq_tier_0);
180 assert!(av1c.high_bitdepth);
181 assert!(!av1c.twelve_bit);
182 assert!(av1c.chroma_subsampling_x);
183 assert!(av1c.chroma_subsampling_y);
184 assert_eq!(av1c.chroma_sample_position, 2);
185 assert!(av1c.config_obus.is_empty());
186
187 let back = av1_from_av1c(&av1c);
189 assert_eq!(back.profile, av1.profile);
190 assert_eq!(back.level, av1.level);
191 assert_eq!(back.bitdepth, av1.bitdepth);
192 assert_eq!(back.mono_chrome, av1.mono_chrome);
193 assert_eq!(back.chroma_subsampling_x, av1.chroma_subsampling_x);
194 assert_eq!(back.chroma_subsampling_y, av1.chroma_subsampling_y);
195 assert_eq!(back.chroma_sample_position, av1.chroma_sample_position);
196 }
197}