1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum NalMuxCodec {
14 H264,
15 H265,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20enum NalClass {
21 Vps,
22 Sps,
23 Pps,
24 Sample,
26}
27
28fn nal_type(nal: &[u8], codec: NalMuxCodec) -> u8 {
30 if nal.is_empty() {
31 return 0;
32 }
33 match codec {
34 NalMuxCodec::H264 => nal[0] & 0x1F, NalMuxCodec::H265 => (nal[0] >> 1) & 0x3F, }
37}
38
39fn classify(nal: &[u8], codec: NalMuxCodec) -> NalClass {
41 match (codec, nal_type(nal, codec)) {
42 (NalMuxCodec::H264, 7) => NalClass::Sps,
43 (NalMuxCodec::H264, 8) => NalClass::Pps,
44 (NalMuxCodec::H265, 32) => NalClass::Vps,
45 (NalMuxCodec::H265, 33) => NalClass::Sps,
46 (NalMuxCodec::H265, 34) => NalClass::Pps,
47 _ => NalClass::Sample,
48 }
49}
50
51fn is_aud(nal: &[u8], codec: NalMuxCodec) -> bool {
53 match codec {
54 NalMuxCodec::H264 => nal_type(nal, codec) == 9,
55 NalMuxCodec::H265 => nal_type(nal, codec) == 35,
56 }
57}
58
59fn is_idr(nal: &[u8], codec: NalMuxCodec) -> bool {
61 match codec {
62 NalMuxCodec::H264 => nal_type(nal, codec) == 5, NalMuxCodec::H265 => matches!(nal_type(nal, codec), 16..=23), }
65}
66
67fn is_vcl(nal: &[u8], codec: NalMuxCodec) -> bool {
69 let t = nal_type(nal, codec);
70 match codec {
71 NalMuxCodec::H264 => (1..=5).contains(&t),
72 NalMuxCodec::H265 => t <= 31,
73 }
74}
75
76fn first_slice_in_pic(nal: &[u8], codec: NalMuxCodec) -> bool {
81 match codec {
82 NalMuxCodec::H264 => nal.len() > 1 && (nal[1] & 0x80) != 0,
83 NalMuxCodec::H265 => nal.len() > 2 && (nal[2] & 0x80) != 0,
84 }
85}
86
87#[derive(Debug, Clone)]
90pub struct AuSample {
91 pub data: Vec<u8>,
92 pub is_keyframe: bool,
93}
94
95pub fn split_annexb_nals(data: &[u8]) -> Vec<&[u8]> {
98 let mut nals = Vec::new();
99 let n = data.len();
100 let mut cursor = match find_start_code(data, 0) {
102 Some((pos, len)) => pos + len,
103 None => return nals, };
105 loop {
106 let (next_pos, next_len) = match find_start_code(data, cursor) {
110 Some(x) => x,
111 None => {
112 if n > cursor {
113 nals.push(&data[cursor..n]); }
115 break;
116 }
117 };
118 if next_pos > cursor {
119 nals.push(&data[cursor..next_pos]);
120 }
121 cursor = next_pos + next_len;
122 }
123 nals
124}
125
126fn find_start_code(data: &[u8], from: usize) -> Option<(usize, usize)> {
133 let n = data.len();
134 let mut i = from;
135 while i + 3 <= n {
136 if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1 {
137 return Some((i, 3));
138 }
139 i += 1;
140 }
141 None
142}
143
144#[derive(Debug)]
157pub struct NalSampleWriter {
158 codec: NalMuxCodec,
159 pub vps: Vec<Vec<u8>>,
161 pub sps: Vec<Vec<u8>>,
162 pub pps: Vec<Vec<u8>>,
163 inline_param_sets: bool,
164}
165
166impl NalSampleWriter {
167 pub fn new(codec: NalMuxCodec) -> Self {
168 Self { codec, vps: Vec::new(), sps: Vec::new(), pps: Vec::new(), inline_param_sets: false }
169 }
170
171 pub fn new_inline(codec: NalMuxCodec) -> Self {
174 Self { codec, vps: Vec::new(), sps: Vec::new(), pps: Vec::new(), inline_param_sets: true }
175 }
176
177 pub fn push_packet(&mut self, annexb: &[u8]) -> Vec<AuSample> {
183 let mut units: Vec<Vec<&[u8]>> = vec![Vec::new()];
187 let mut cur_has_vcl = false;
188 for nal in split_annexb_nals(annexb) {
189 let new_au = is_aud(nal, self.codec)
190 || (is_vcl(nal, self.codec) && cur_has_vcl && first_slice_in_pic(nal, self.codec));
191 if new_au && !units.last().unwrap().is_empty() {
192 units.push(Vec::new());
193 cur_has_vcl = false;
194 }
195 if is_vcl(nal, self.codec) {
196 cur_has_vcl = true;
197 }
198 units.last_mut().unwrap().push(nal);
199 }
200
201 let codec = self.codec;
202 let inline = self.inline_param_sets;
203 let mut samples = Vec::new();
204 for unit in units {
205 let mut data = Vec::new();
206 let mut is_keyframe = false;
207 for nal in unit {
208 let push_inline = |data: &mut Vec<u8>| {
209 data.extend_from_slice(&(nal.len() as u32).to_be_bytes());
210 data.extend_from_slice(nal);
211 };
212 match classify(nal, codec) {
213 NalClass::Sample => {
214 if is_idr(nal, codec) {
215 is_keyframe = true;
216 }
217 push_inline(&mut data);
218 continue;
219 }
220 NalClass::Vps | NalClass::Sps | NalClass::Pps => {}
221 }
222 let store = match classify(nal, codec) {
224 NalClass::Vps => &mut self.vps,
225 NalClass::Sps => &mut self.sps,
226 NalClass::Pps => &mut self.pps,
227 NalClass::Sample => unreachable!(),
228 };
229 if inline {
230 if store.is_empty() {
233 store.push(nal.to_vec());
234 }
235 push_inline(&mut data);
236 } else {
237 dedup_push(store, nal);
238 }
239 }
240 if !data.is_empty() {
241 samples.push(AuSample { data, is_keyframe });
242 }
243 }
244 samples
245 }
246
247 pub fn has_param_sets(&self) -> bool {
249 let vps_ok = matches!(self.codec, NalMuxCodec::H264) || !self.vps.is_empty();
250 vps_ok && !self.sps.is_empty() && !self.pps.is_empty()
251 }
252}
253
254fn dedup_push(set: &mut Vec<Vec<u8>>, nal: &[u8]) {
255 if !set.iter().any(|n| n.as_slice() == nal) {
256 set.push(nal.to_vec());
257 }
258}
259
260#[cfg(test)]
261mod tests {
262 use super::*;
263
264 fn sc4(nal: &[u8]) -> Vec<u8> {
265 let mut v = vec![0, 0, 0, 1];
266 v.extend_from_slice(nal);
267 v
268 }
269
270 #[test]
271 fn splits_3_and_4_byte_start_codes() {
272 let mut buf = vec![0, 0, 0, 1, 0xAA, 0xBB];
274 buf.extend_from_slice(&[0, 0, 1, 0xCC]);
275 let nals = split_annexb_nals(&buf);
276 assert_eq!(nals.len(), 2);
277 assert_eq!(nals[0], &[0xAA, 0xBB]);
278 assert_eq!(nals[1], &[0xCC]);
279 }
280
281 #[test]
282 fn h264_strips_sps_pps_keeps_slice() {
283 let sps = [0x67u8, 0x42, 0x00, 0x1e, 0xAA];
285 let pps = [0x68u8, 0xCE, 0x3C];
286 let idr = [0x65u8, 0x88, 0x11, 0x22];
287 let mut frame = sc4(&sps);
288 frame.extend(sc4(&pps));
289 frame.extend(sc4(&idr));
290 let mut w = NalSampleWriter::new(NalMuxCodec::H264);
291 let samples = w.push_packet(&frame);
292 assert_eq!(samples.len(), 1, "no AUD → one access unit");
293 assert!(samples[0].is_keyframe, "contains an IDR slice");
294 assert_eq!(w.sps.len(), 1);
297 assert!(w.sps[0].starts_with(&sps));
298 assert!(w.pps[0].starts_with(&pps));
299 assert!(w.has_param_sets());
300 let mut expect = (idr.len() as u32).to_be_bytes().to_vec();
302 expect.extend_from_slice(&idr);
303 assert_eq!(samples[0].data, expect);
304 }
305
306 #[test]
307 fn splits_multi_au_packet_by_aud() {
308 let aud = [0x09u8, 0x10];
310 let idr = [0x65u8, 0x11];
311 let p = [0x41u8, 0x22];
312 let mut frame = sc4(&aud);
313 frame.extend(sc4(&idr)); frame.extend(sc4(&aud));
315 frame.extend(sc4(&p)); let mut w = NalSampleWriter::new(NalMuxCodec::H264);
317 let samples = w.push_packet(&frame);
318 assert_eq!(samples.len(), 2, "two AUDs → two samples");
319 assert!(samples[0].is_keyframe, "AU1 has the IDR");
320 assert!(!samples[1].is_keyframe, "AU2 is a P-frame");
321 }
322
323 #[test]
324 fn inline_mode_keeps_param_sets_in_sample() {
325 let sps = [0x67u8, 0x42, 0x00, 0x1e, 0xAA];
328 let pps = [0x68u8, 0xCE, 0x3C];
329 let idr = [0x65u8, 0x88, 0x11, 0x22];
330 let mut frame = sc4(&sps);
331 frame.extend(sc4(&pps));
332 frame.extend(sc4(&idr));
333
334 let mut w = NalSampleWriter::new_inline(NalMuxCodec::H264);
335 let inline = w.push_packet(&frame);
336 assert_eq!(inline.len(), 1);
337 assert!(inline[0].is_keyframe);
338 assert_eq!(w.sps.len(), 1);
340 assert!(w.sps[0].starts_with(&sps));
341 assert_eq!(w.pps.len(), 1);
342
343 let mut w2 = NalSampleWriter::new(NalMuxCodec::H264);
345 let oob = w2.push_packet(&frame);
346 assert!(
347 inline[0].data.len() > oob[0].data.len(),
348 "inline sample (SPS+PPS+IDR) must be larger than the stripped one ({} vs {})",
349 inline[0].data.len(),
350 oob[0].data.len()
351 );
352 assert_eq!(&inline[0].data[4..4 + sps.len()], &sps);
354 }
355
356 #[test]
357 fn h265_splits_multi_picture_packet_without_aud() {
358 let idr = [0x26u8, 0x01, 0xA0]; let trail = [0x02u8, 0x01, 0xA0]; let mut frame = sc4(&idr);
362 frame.extend(sc4(&trail));
363 let mut w = NalSampleWriter::new(NalMuxCodec::H265);
364 let samples = w.push_packet(&frame);
365 assert_eq!(samples.len(), 2, "two first-slice VCL NALs → two access units");
366 assert!(samples[0].is_keyframe);
367 assert!(!samples[1].is_keyframe);
368 }
369
370 #[test]
371 fn h265_captures_vps_sps_pps() {
372 let vps = [0x40u8, 0x01, 0x0c]; let sps = [0x42u8, 0x01, 0x01]; let pps = [0x44u8, 0x01, 0xc1]; let slice = [0x26u8, 0x01, 0xaf]; let mut frame = sc4(&vps);
377 frame.extend(sc4(&sps));
378 frame.extend(sc4(&pps));
379 frame.extend(sc4(&slice));
380 let mut w = NalSampleWriter::new(NalMuxCodec::H265);
381 let samples = w.push_packet(&frame);
382 assert_eq!(samples.len(), 1);
383 assert!(samples[0].is_keyframe, "type 19 is an IRAP/IDR");
384 assert!(w.vps[0].starts_with(&vps));
385 assert!(w.sps[0].starts_with(&sps));
386 assert!(w.pps[0].starts_with(&pps));
387 assert!(w.has_param_sets());
388 let mut expect = (slice.len() as u32).to_be_bytes().to_vec();
389 expect.extend_from_slice(&slice);
390 assert_eq!(samples[0].data, expect);
391 }
392
393 #[test]
394 fn preserves_slice_trailing_zero_bytes() {
395 let slice = [0x65u8, 0x88, 0x00, 0x00, 0x00];
398 let next = [0x41u8, 0x9a]; let mut frame = sc4(&slice);
400 frame.extend(sc4(&next));
401 let nals = split_annexb_nals(&frame);
402 assert_eq!(nals.len(), 2);
403 assert!(nals[0].starts_with(&slice), "slice trailing zeros must survive: {:?}", nals[0]);
406 assert!(nals[1].starts_with(&next));
407 let mut f2 = sc4(&slice);
409 f2.extend_from_slice(&[0, 0, 1]);
410 f2.extend_from_slice(&next);
411 let n2 = split_annexb_nals(&f2);
412 assert_eq!(n2[0], &slice, "trailing zeros kept exactly with a 3-byte next start code");
413 }
414
415 #[test]
416 fn dedups_repeated_param_sets() {
417 let sps = [0x67u8, 0x42, 0x00, 0x1e];
418 let pps = [0x68u8, 0xCE, 0x3C];
419 let idr = [0x65u8, 0x88];
420 let mut w = NalSampleWriter::new(NalMuxCodec::H264);
421 for _ in 0..2 {
423 let mut f = sc4(&sps);
424 f.extend(sc4(&pps));
425 f.extend(sc4(&idr));
426 w.push_packet(&f);
427 }
428 assert_eq!(w.sps.len(), 1);
429 assert_eq!(w.pps.len(), 1);
430 }
431}