1#![allow(dead_code)]
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum H264NalType {
11 Unspecified,
13 NonIdrSlice,
15 SlicePartitionA,
17 SlicePartitionB,
19 SlicePartitionC,
21 IdrSlice,
23 Sei,
25 Sps,
27 Pps,
29 AccessUnitDelimiter,
31 EndOfSequence,
33 EndOfStream,
35 FillerData,
37 Reserved(u8),
39}
40
41impl H264NalType {
42 #[must_use]
45 pub fn from_header_byte(byte: u8) -> Self {
46 match byte & 0x1F {
47 0 => Self::Unspecified,
48 1 => Self::NonIdrSlice,
49 2 => Self::SlicePartitionA,
50 3 => Self::SlicePartitionB,
51 4 => Self::SlicePartitionC,
52 5 => Self::IdrSlice,
53 6 => Self::Sei,
54 7 => Self::Sps,
55 8 => Self::Pps,
56 9 => Self::AccessUnitDelimiter,
57 10 => Self::EndOfSequence,
58 11 => Self::EndOfStream,
59 12 => Self::FillerData,
60 n => Self::Reserved(n),
61 }
62 }
63
64 #[must_use]
66 pub fn is_slice(self) -> bool {
67 matches!(self, Self::NonIdrSlice | Self::IdrSlice)
68 }
69
70 #[must_use]
72 pub fn is_parameter_set(self) -> bool {
73 matches!(self, Self::Sps | Self::Pps)
74 }
75}
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub enum H265NalType {
80 TrailN,
82 TrailR,
84 IdrWRadl,
86 IdrNLp,
88 Vps,
90 Sps,
92 Pps,
94 Aud,
96 PrefixSei,
98 Reserved(u8),
100}
101
102impl H265NalType {
103 #[must_use]
105 pub fn from_nal_type(nal_type: u8) -> Self {
106 match nal_type & 0x3F {
107 0 => Self::TrailN,
108 1 => Self::TrailR,
109 19 => Self::IdrWRadl,
110 20 => Self::IdrNLp,
111 32 => Self::Vps,
112 33 => Self::Sps,
113 34 => Self::Pps,
114 35 => Self::Aud,
115 39 => Self::PrefixSei,
116 n => Self::Reserved(n),
117 }
118 }
119
120 #[must_use]
122 pub fn is_idr(self) -> bool {
123 matches!(self, Self::IdrWRadl | Self::IdrNLp)
124 }
125}
126
127pub const START_CODE_3: [u8; 3] = [0x00, 0x00, 0x01];
129pub const START_CODE_4: [u8; 4] = [0x00, 0x00, 0x00, 0x01];
131
132#[must_use]
134pub fn has_start_code(data: &[u8]) -> bool {
135 data.starts_with(&START_CODE_4) || data.starts_with(&START_CODE_3)
136}
137
138#[must_use]
141pub fn strip_rbsp_trailing(data: &[u8]) -> &[u8] {
142 let mut end = data.len();
144 while end > 0 {
145 end -= 1;
146 let byte = data[end];
147 if byte == 0x80 {
148 return &data[..end];
149 }
150 if byte != 0x00 {
151 return data;
153 }
154 }
155 data
156}
157
158#[derive(Debug, Clone)]
160pub struct NalUnit<'a> {
161 pub data: &'a [u8],
163 pub offset: usize,
165}
166
167impl NalUnit<'_> {
168 #[must_use]
170 pub fn header_byte(&self) -> Option<u8> {
171 self.data.first().copied()
172 }
173
174 #[must_use]
176 pub fn h264_type(&self) -> Option<H264NalType> {
177 self.header_byte().map(H264NalType::from_header_byte)
178 }
179}
180
181#[must_use]
185pub fn parse_annex_b(data: &[u8]) -> Vec<NalUnit<'_>> {
186 let mut units = Vec::new();
187 let mut i = 0usize;
188 let len = data.len();
189
190 while i < len {
191 let start_code_len = if i + 4 <= len && data[i..i + 4] == START_CODE_4 {
193 4
194 } else if i + 3 <= len && data[i..i + 3] == START_CODE_3 {
195 3
196 } else {
197 i += 1;
198 continue;
199 };
200
201 let nal_start = i + start_code_len;
202 let mut j = nal_start;
204 let mut found_next = false;
205 while j + 3 <= len {
206 if data[j..j + 3] == START_CODE_3 || (j + 4 <= len && data[j..j + 4] == START_CODE_4) {
207 found_next = true;
208 break;
209 }
210 j += 1;
211 }
212 if !found_next {
213 j = len;
214 }
215 let mut nal_end = j;
217 while nal_end > nal_start && data[nal_end - 1] == 0x00 {
218 nal_end -= 1;
219 }
220 if nal_end > nal_start {
221 units.push(NalUnit {
222 data: &data[nal_start..nal_end],
223 offset: nal_start,
224 });
225 }
226 i = j;
227 }
228 units
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234
235 #[test]
236 fn test_h264_idr_type() {
237 assert_eq!(H264NalType::from_header_byte(0x65), H264NalType::IdrSlice);
238 }
239
240 #[test]
241 fn test_h264_sps_type() {
242 assert_eq!(H264NalType::from_header_byte(0x67), H264NalType::Sps);
243 }
244
245 #[test]
246 fn test_h264_pps_type() {
247 assert_eq!(H264NalType::from_header_byte(0x68), H264NalType::Pps);
248 }
249
250 #[test]
251 fn test_h264_non_idr_slice() {
252 assert_eq!(
253 H264NalType::from_header_byte(0x41),
254 H264NalType::NonIdrSlice
255 );
256 }
257
258 #[test]
259 fn test_h264_is_slice() {
260 assert!(H264NalType::IdrSlice.is_slice());
261 assert!(H264NalType::NonIdrSlice.is_slice());
262 assert!(!H264NalType::Sps.is_slice());
263 }
264
265 #[test]
266 fn test_h264_is_parameter_set() {
267 assert!(H264NalType::Sps.is_parameter_set());
268 assert!(H264NalType::Pps.is_parameter_set());
269 assert!(!H264NalType::IdrSlice.is_parameter_set());
270 }
271
272 #[test]
273 fn test_h265_idr_type() {
274 assert_eq!(H265NalType::from_nal_type(19), H265NalType::IdrWRadl);
275 assert!(H265NalType::IdrWRadl.is_idr());
276 }
277
278 #[test]
279 fn test_h265_non_idr_not_idr() {
280 assert!(!H265NalType::TrailR.is_idr());
281 }
282
283 #[test]
284 fn test_has_start_code_4byte() {
285 assert!(has_start_code(&[0x00, 0x00, 0x00, 0x01, 0x67]));
286 }
287
288 #[test]
289 fn test_has_start_code_3byte() {
290 assert!(has_start_code(&[0x00, 0x00, 0x01, 0x67]));
291 }
292
293 #[test]
294 fn test_no_start_code() {
295 assert!(!has_start_code(&[0x01, 0x02, 0x03]));
296 }
297
298 #[test]
299 fn test_strip_rbsp_trailing_removes_stop_bit() {
300 let data = &[0xAB, 0xCD, 0x80, 0x00, 0x00];
301 let stripped = strip_rbsp_trailing(data);
302 assert_eq!(stripped, &[0xAB, 0xCD]);
303 }
304
305 #[test]
306 fn test_strip_rbsp_no_stop_bit_returns_original() {
307 let data = &[0x01, 0x02, 0x03];
308 assert_eq!(strip_rbsp_trailing(data), data);
309 }
310
311 #[test]
312 fn test_parse_annex_b_single_nal() {
313 let stream = [0x00, 0x00, 0x00, 0x01, 0x67, 0xAB, 0xCD];
314 let units = parse_annex_b(&stream);
315 assert_eq!(units.len(), 1);
316 assert_eq!(units[0].data[0], 0x67);
317 }
318
319 #[test]
320 fn test_parse_annex_b_multiple_nals() {
321 let stream = [
322 0x00, 0x00, 0x00, 0x01, 0x67, 0x11, 0x00, 0x00, 0x01, 0x68, 0x22,
323 ];
324 let units = parse_annex_b(&stream);
325 assert_eq!(units.len(), 2);
326 assert_eq!(units[0].h264_type(), Some(H264NalType::Sps));
327 assert_eq!(units[1].h264_type(), Some(H264NalType::Pps));
328 }
329}