1use super::bitreader::BitReader;
14use super::nal::{conformance_dims, iter_nals, unescape_rbsp};
15use super::parser::{CodecParser, VideoParams};
16use crate::CodecId;
17
18pub const NAL_VPS: u8 = 14;
20pub const NAL_SPS: u8 = 15;
22pub const NAL_PPS: u8 = 16;
24
25fn nal_type(nal: &[u8]) -> Option<u8> {
27 nal.get(1).map(|b| (b >> 3) & 0x1F)
28}
29
30fn is_irap(t: u8) -> bool {
32 (7..=9).contains(&t)
33}
34
35const GCI_FIXED_FLAG_BITS: usize = 71;
40
41fn parse_profile_tier_level(r: &mut BitReader, max_sublayers_minus1: u32) -> Option<(u8, u8, u8)> {
46 let general_profile_idc = r.read_bits(7)? as u8;
47 let general_tier_flag = r.read_bit()? as u8;
48 let general_level_idc = r.read_bits(8)? as u8;
49 let _ptl_frame_only_constraint_flag = r.read_bit()?;
50 let _ptl_multilayer_enabled_flag = r.read_bit()?;
51
52 if r.read_bit()? == 1 {
54 r.skip_bits(GCI_FIXED_FLAG_BITS)?;
56 let gci_num_additional_bits = r.read_bits(8)? as usize;
57 r.skip_bits(gci_num_additional_bits)?;
58 }
59 r.align_to_byte(); let mut sublayer_present = vec![false; max_sublayers_minus1 as usize];
63 for i in (0..max_sublayers_minus1 as usize).rev() {
64 sublayer_present[i] = r.read_bit()? == 1;
65 }
66 r.align_to_byte(); for present in sublayer_present.iter().rev() {
68 if *present {
69 let _sublayer_level_idc = r.read_bits(8)?;
70 }
71 }
72
73 let num_sub_profiles = r.read_bits(8)?;
75 for _ in 0..num_sub_profiles {
76 let _general_sub_profile_idc = r.read_bits(32)?;
77 }
78
79 Some((general_profile_idc, general_tier_flag, general_level_idc))
80}
81
82fn parse_sps(nal: &[u8]) -> Option<VideoParams> {
84 if nal.len() < 3 || nal_type(nal)? != NAL_SPS {
85 return None;
86 }
87 let rbsp = unescape_rbsp(&nal[2..]);
88 let mut r = BitReader::new(&rbsp);
89
90 let _sps_seq_parameter_set_id = r.read_bits(4)?;
91 let _sps_video_parameter_set_id = r.read_bits(4)?;
92 let sps_max_sublayers_minus1 = r.read_bits(3)?;
93 let chroma_format_idc = r.read_bits(2)?;
94 let _sps_log2_ctu_size_minus5 = r.read_bits(2)?;
95 let ptl_present = r.read_bit()?;
96 let (profile, tier, level) = if ptl_present == 1 {
97 parse_profile_tier_level(&mut r, sps_max_sublayers_minus1)?
98 } else {
99 (0, 0, 0)
100 };
101
102 let _sps_gdr_enabled_flag = r.read_bit()?;
103 let sps_ref_pic_resampling = r.read_bit()?;
104 if sps_ref_pic_resampling == 1 {
105 let _sps_res_change_in_clvs_allowed = r.read_bit()?;
106 }
107
108 let width_luma = r.read_ue()?;
109 let height_luma = r.read_ue()?;
110 let (mut l, mut rr, mut t, mut b) = (0, 0, 0, 0);
111 if r.read_bit()? == 1 {
112 l = r.read_ue()?;
113 rr = r.read_ue()?;
114 t = r.read_ue()?;
115 b = r.read_ue()?;
116 }
117 let (width, height) = conformance_dims(width_luma, height_luma, chroma_format_idc, l, rr, t, b);
118
119 Some(VideoParams {
120 width,
121 height,
122 profile,
123 level,
124 tier,
125 bit_depth: 8,
126 })
127}
128
129pub fn vvcc_config_record(data: &[u8]) -> Option<(VideoParams, Vec<u8>)> {
141 let params = Vvc::parse_config(data)?;
142
143 let mut vps: Vec<&[u8]> = Vec::new();
145 let mut sps: Vec<&[u8]> = Vec::new();
146 let mut pps: Vec<&[u8]> = Vec::new();
147 for nal in iter_nals(data) {
148 match nal_type(nal) {
149 Some(NAL_VPS) => vps.push(nal),
150 Some(NAL_SPS) => sps.push(nal),
151 Some(NAL_PPS) => pps.push(nal),
152 _ => {}
153 }
154 }
155 if sps.is_empty() {
156 return None;
157 }
158
159 let chroma_format_idc = 1u8; let bit_depth_minus8 = params.bit_depth.saturating_sub(8);
161
162 let mut rec = vec![
164 0xFF, 0x00, 0x10 | (chroma_format_idc & 0x03), (bit_depth_minus8 << 5) | 0x1F, 0x01, ((params.profile & 0x7F) << 1) | (params.tier & 1), params.level, 0x00, 0x00, ];
174 rec.extend_from_slice(&(params.width as u16).to_be_bytes()); rec.extend_from_slice(&(params.height as u16).to_be_bytes()); rec.extend_from_slice(&0u16.to_be_bytes()); let groups = [(NAL_VPS, &vps), (NAL_SPS, &sps), (NAL_PPS, &pps)];
180 let present: Vec<_> = groups.iter().filter(|(_, v)| !v.is_empty()).collect();
181 rec.push(present.len() as u8); for (t, nals) in present {
183 rec.push(0x80 | (t & 0x1F)); rec.extend_from_slice(&(nals.len() as u16).to_be_bytes());
185 for nal in nals.iter() {
186 rec.extend_from_slice(&(nal.len() as u16).to_be_bytes());
187 rec.extend_from_slice(nal);
188 }
189 }
190 Some((params, rec))
191}
192
193pub struct Vvc;
195
196impl CodecParser for Vvc {
197 const CODEC: CodecId = CodecId::VVC;
198
199 fn parse_config(data: &[u8]) -> Option<VideoParams> {
200 iter_nals(data).find_map(parse_sps)
201 }
202
203 fn is_random_access_point(data: &[u8]) -> bool {
204 iter_nals(data).any(|n| nal_type(n).is_some_and(|t| t <= 11 && is_irap(t)))
205 }
206
207 fn carries_config(data: &[u8]) -> bool {
208 iter_nals(data)
209 .any(|n| nal_type(n).is_some_and(|t| matches!(t, NAL_VPS | NAL_SPS | NAL_PPS)))
210 }
211
212 fn hls_codec_string(p: &VideoParams) -> String {
213 format!("vvc1.{}.L{}", p.profile, p.level)
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220 use crate::codec::testutil::BitWriter;
221
222 fn sps_1920x1080() -> Vec<u8> {
224 let mut w = BitWriter::default();
225 w.bits(0, 4); w.bits(0, 4); w.bits(0, 3); w.bits(1, 2); w.bits(0, 2); w.bit(0); w.bit(0); w.bit(0); w.ue(1920); w.ue(1080); w.bit(0); let mut nal = vec![0x00u8, 0x79]; nal.extend_from_slice(&w.bytes());
238 nal
239 }
240
241 fn sps_with_ptl(profile: u32, tier: u8, level: u32, gci_present: bool) -> Vec<u8> {
245 let mut w = BitWriter::default();
246 w.bits(0, 4); w.bits(0, 4); w.bits(0, 3); w.bits(1, 2); w.bits(0, 2); w.bit(1); w.bits(profile, 7); w.bit(tier); w.bits(level, 8); w.bit(0); w.bit(0); w.bit(gci_present as u8); if gci_present {
260 for _ in 0..71 {
261 w.bit(0); }
263 w.bits(0, 8); }
265 w.align(); w.align();
268 w.bits(0, 8); w.bit(0); w.bit(0); w.ue(1280); w.ue(720); w.bit(0); let mut nal = vec![0x00u8, 0x79]; nal.extend_from_slice(&w.bytes());
277 nal
278 }
279
280 #[test]
281 fn parse_sps_extracts_resolution() {
282 let p = parse_sps(&sps_1920x1080()).expect("parse");
283 assert_eq!((p.width, p.height), (1920, 1080));
284 assert_eq!((p.profile, p.level, p.tier), (0, 0, 0));
286 }
287
288 #[test]
289 fn parse_sps_with_ptl_extracts_profile_level_and_dims() {
290 let p = parse_sps(&sps_with_ptl(1, 0, 51, false)).expect("parse no-gci");
292 assert_eq!((p.width, p.height), (1280, 720));
293 assert_eq!((p.profile, p.level, p.tier), (1, 51, 0));
294
295 let p = parse_sps(&sps_with_ptl(1, 1, 51, true)).expect("parse gci");
297 assert_eq!((p.width, p.height), (1280, 720));
298 assert_eq!((p.profile, p.level, p.tier), (1, 51, 1));
299 assert_eq!(Vvc::hls_codec_string(&p), "vvc1.1.L51");
300 }
301
302 #[test]
303 fn classifies_irap_and_config() {
304 let mut au = vec![0, 0, 0, 1];
305 au.extend_from_slice(&sps_1920x1080());
306 au.extend_from_slice(&[0, 0, 0, 1, 0x00, 0x39, 0xAA]);
308
309 assert!(Vvc::is_random_access_point(&au));
310 assert!(Vvc::carries_config(&au));
311 assert_eq!(Vvc::parse_config(&au).expect("params").width, 1920);
312
313 assert!(!Vvc::is_random_access_point(&[
315 0, 0, 0, 1, 0x00, 0x01, 0xAA
316 ]));
317 }
318
319 #[test]
320 fn vvcc_record_embeds_param_sets_and_dims() {
321 let vps = [0x00u8, 0x71, 0xAB, 0xCD];
323 let sps = sps_1920x1080();
324 let mut au = vec![0, 0, 0, 1];
325 au.extend_from_slice(&vps);
326 au.extend_from_slice(&[0, 0, 0, 1]);
327 au.extend_from_slice(&sps);
328
329 let (params, rec) = vvcc_config_record(&au).expect("record");
330 assert_eq!((params.width, params.height), (1920, 1080));
331 assert_eq!(rec[0], 0xFF); assert_eq!(rec[2] & 0x03, 1); assert!(rec.windows(vps.len()).any(|w| w == vps), "VPS embedded");
337 assert!(rec.windows(sps.len()).any(|w| w == sps), "SPS embedded");
338 assert!(rec.contains(&0x8F));
340 }
341}