arcly_stream/codec/
vvc.rs1use 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
35fn parse_sps(nal: &[u8]) -> Option<VideoParams> {
37 if nal.len() < 3 || nal_type(nal)? != NAL_SPS {
38 return None;
39 }
40 let rbsp = unescape_rbsp(&nal[2..]);
41 let mut r = BitReader::new(&rbsp);
42
43 let _sps_seq_parameter_set_id = r.read_bits(4)?;
44 let _sps_video_parameter_set_id = r.read_bits(4)?;
45 let _sps_max_sublayers_minus1 = r.read_bits(3)?;
46 let chroma_format_idc = r.read_bits(2)?;
47 let _sps_log2_ctu_size_minus5 = r.read_bits(2)?;
48 let ptl_present = r.read_bit()?;
49 if ptl_present == 1 {
50 return None;
53 }
54
55 let _sps_gdr_enabled_flag = r.read_bit()?;
56 let sps_ref_pic_resampling = r.read_bit()?;
57 if sps_ref_pic_resampling == 1 {
58 let _sps_res_change_in_clvs_allowed = r.read_bit()?;
59 }
60
61 let width_luma = r.read_ue()?;
62 let height_luma = r.read_ue()?;
63 let (mut l, mut rr, mut t, mut b) = (0, 0, 0, 0);
64 if r.read_bit()? == 1 {
65 l = r.read_ue()?;
66 rr = r.read_ue()?;
67 t = r.read_ue()?;
68 b = r.read_ue()?;
69 }
70 let (width, height) = conformance_dims(width_luma, height_luma, chroma_format_idc, l, rr, t, b);
71
72 Some(VideoParams {
73 width,
74 height,
75 profile: 0,
76 level: 0,
77 tier: 0,
78 bit_depth: 8,
79 })
80}
81
82pub struct Vvc;
84
85impl CodecParser for Vvc {
86 const CODEC: CodecId = CodecId::VVC;
87
88 fn parse_config(data: &[u8]) -> Option<VideoParams> {
89 iter_nals(data).find_map(parse_sps)
90 }
91
92 fn is_random_access_point(data: &[u8]) -> bool {
93 iter_nals(data).any(|n| nal_type(n).is_some_and(|t| t <= 11 && is_irap(t)))
94 }
95
96 fn carries_config(data: &[u8]) -> bool {
97 iter_nals(data)
98 .any(|n| nal_type(n).is_some_and(|t| matches!(t, NAL_VPS | NAL_SPS | NAL_PPS)))
99 }
100
101 fn hls_codec_string(p: &VideoParams) -> String {
102 format!("vvc1.{}.L{}", p.profile, p.level)
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109 use crate::codec::testutil::BitWriter;
110
111 fn sps_1920x1080() -> Vec<u8> {
113 let mut w = BitWriter::default();
114 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());
127 nal
128 }
129
130 #[test]
131 fn parse_sps_extracts_resolution() {
132 let p = parse_sps(&sps_1920x1080()).expect("parse");
133 assert_eq!((p.width, p.height), (1920, 1080));
134 }
135
136 #[test]
137 fn classifies_irap_and_config() {
138 let mut au = vec![0, 0, 0, 1];
139 au.extend_from_slice(&sps_1920x1080());
140 au.extend_from_slice(&[0, 0, 0, 1, 0x00, 0x39, 0xAA]);
142
143 assert!(Vvc::is_random_access_point(&au));
144 assert!(Vvc::carries_config(&au));
145 assert_eq!(Vvc::parse_config(&au).expect("params").width, 1920);
146
147 assert!(!Vvc::is_random_access_point(&[
149 0, 0, 0, 1, 0x00, 0x01, 0xAA
150 ]));
151 }
152}