1mod export;
11mod import;
12mod split;
13
14pub use export::*;
15pub use import::*;
16pub use split::*;
17
18use bytes::{Buf, BufMut, Bytes, BytesMut};
19use scuffle_h265::{NALUnitType, SpsNALUnit};
20
21pub(crate) fn hvc1_frame(
29 data: impl moq_net::IntoBytes,
30 length_size: usize,
31 pts: moq_net::Timestamp,
32) -> crate::Result<crate::container::Frame> {
33 let keyframe = hvc1_is_keyframe(data.as_ref(), length_size);
34 Ok(crate::container::Frame {
35 timestamp: pts,
36 payload: data.into_bytes(),
37 keyframe,
38 duration: None,
39 })
40}
41
42fn hvc1_is_keyframe(data: &[u8], length_size: usize) -> bool {
44 let Ok(nals) = crate::codec::annexb::length_prefixed_nals(data, length_size) else {
45 return false;
46 };
47 nals.map_while(std::result::Result::ok)
48 .any(|nal| nal.first().is_some_and(|header| is_irap(split::nal_unit_type(*header))))
49}
50
51pub(crate) fn is_irap(nal_type: NALUnitType) -> bool {
54 matches!(
55 nal_type,
56 NALUnitType::IdrWRadl
57 | NALUnitType::IdrNLp
58 | NALUnitType::BlaNLp
59 | NALUnitType::BlaWRadl
60 | NALUnitType::BlaWLp
61 | NALUnitType::CraNut
62 )
63}
64
65#[derive(Debug, Clone, thiserror::Error)]
67#[non_exhaustive]
68pub enum Error {
69 #[error("NAL unit is too short")]
70 NalTooShort,
71
72 #[error("{0} too large for hvcC length field ({1} > {max})", max = u16::MAX)]
73 NalTooLargeForHvcc(&'static str, usize),
74
75 #[error("too many {0} for hvcC ({1} > {max})", max = u16::MAX)]
76 TooManyNals(&'static str, usize),
77
78 #[error("NAL too large for 4-byte length prefix")]
79 NalTooLarge,
80
81 #[error("failed to parse SPS NAL unit")]
82 SpsParse,
83
84 #[error("missing level_idc in SPS")]
85 MissingLevelIdc,
86
87 #[error("forbidden zero bit is not zero")]
88 ForbiddenZeroBit,
89
90 #[error("not initialized")]
91 NotInitialized,
92
93 #[error("expected SPS before any frames")]
94 MissingSps,
95
96 #[error("missing timestamp")]
97 MissingTimestamp,
98
99 #[error("HEVCDecoderConfigurationRecord too short")]
100 HvccTooShort,
101
102 #[error("HEVCDecoderConfigurationRecord truncated")]
103 HvccTruncated,
104
105 #[error("hvc1 description for rendition {name:?} is missing VPS, SPS, or PPS (vps={vps}, sps={sps}, pps={pps})")]
106 MissingParamSets {
107 name: String,
108 vps: usize,
109 sps: usize,
110 pps: usize,
111 },
112
113 #[error("annexb: {0}")]
114 Annexb(#[from] crate::codec::annexb::Error),
115}
116
117pub type Result<T> = std::result::Result<T, Error>;
118
119#[derive(Debug, Clone)]
122#[non_exhaustive]
123pub struct Hvcc {
124 pub length_size: usize,
126 pub vps: Vec<Bytes>,
128 pub sps: Vec<Bytes>,
130 pub pps: Vec<Bytes>,
132}
133
134impl Hvcc {
135 pub fn parse(hvcc: &[u8]) -> Result<Self> {
138 if hvcc.len() < 23 {
139 return Err(Error::HvccTooShort);
140 }
141 let length_size = (hvcc[21] & 0x3) as usize + 1;
142 let num_arrays = hvcc[22] as usize;
143
144 let mut vps = Vec::new();
145 let mut sps = Vec::new();
146 let mut pps = Vec::new();
147 let mut pos: usize = 23;
148
149 for _ in 0..num_arrays {
150 let after_hdr = pos.checked_add(3).ok_or(Error::HvccTruncated)?;
151 if hvcc.len() < after_hdr {
152 return Err(Error::HvccTruncated);
153 }
154 let nal_type = hvcc[pos] & 0x3f;
155 let num_nalus = u16::from_be_bytes([hvcc[pos + 1], hvcc[pos + 2]]) as usize;
156 pos = after_hdr;
157
158 for _ in 0..num_nalus {
159 let after_len = pos.checked_add(2).ok_or(Error::HvccTruncated)?;
160 if hvcc.len() < after_len {
161 return Err(Error::HvccTruncated);
162 }
163 let len = u16::from_be_bytes([hvcc[pos], hvcc[pos + 1]]) as usize;
164 let after_nal = after_len.checked_add(len).ok_or(Error::HvccTruncated)?;
165 if hvcc.len() < after_nal {
166 return Err(Error::HvccTruncated);
167 }
168 let bytes = Bytes::copy_from_slice(&hvcc[after_len..after_nal]);
169 pos = after_nal;
170
171 match NALUnitType::from(nal_type) {
172 NALUnitType::VpsNut => vps.push(bytes),
173 NALUnitType::SpsNut => sps.push(bytes),
174 NALUnitType::PpsNut => pps.push(bytes),
175 _ => {}
176 }
177 }
178 }
179
180 Ok(Self {
181 length_size,
182 vps,
183 sps,
184 pps,
185 })
186 }
187}
188
189pub(crate) fn config_from_hvcc(hvcc: &[u8]) -> Result<hang::catalog::VideoConfig> {
198 let params = Hvcc::parse(hvcc)?;
199 let sps_nal = params.sps.first().ok_or(Error::MissingSps)?;
200 let sps = SpsNALUnit::parse(&mut &sps_nal[..]).map_err(|_| Error::SpsParse)?;
201 let profile = &sps.rbsp.profile_tier_level.general_profile;
202
203 let mut config = hang::catalog::VideoConfig::new(hang::catalog::H265 {
204 in_band: false,
205 profile_space: profile.profile_space,
206 profile_idc: profile.profile_idc,
207 profile_compatibility_flags: profile.profile_compatibility_flag.bits().to_be_bytes(),
208 tier_flag: profile.tier_flag,
209 level_idc: profile.level_idc.ok_or(Error::MissingLevelIdc)?,
210 constraint_flags: pack_constraint_flags(profile),
211 });
212 config.coded_width = Some(sps.rbsp.cropped_width() as u32);
213 config.coded_height = Some(sps.rbsp.cropped_height() as u32);
214 config.description = Some(Bytes::copy_from_slice(hvcc));
215 config.container = hang::catalog::Container::Legacy;
216 Ok(config)
217}
218
219pub struct Hvc1 {
226 hvcc: Option<Bytes>,
227 vps: Vec<Bytes>,
229 sps: Vec<Bytes>,
231 pps: Vec<Bytes>,
233}
234
235impl Default for Hvc1 {
236 fn default() -> Self {
237 Self::new()
238 }
239}
240
241impl Hvc1 {
242 pub fn new() -> Self {
244 Self {
245 hvcc: None,
246 vps: Vec::new(),
247 sps: Vec::new(),
248 pps: Vec::new(),
249 }
250 }
251
252 pub fn hvcc(&self) -> Option<&Bytes> {
254 self.hvcc.as_ref()
255 }
256
257 pub fn transform(&mut self, payload: Bytes) -> Result<Option<Bytes>> {
265 let mut buf = payload.clone();
266 let mut nal_iter = crate::codec::annexb::NalIterator::new(&mut buf);
267
268 let mut out = BytesMut::with_capacity(payload.remaining());
269 let mut frame_vps: Vec<Bytes> = Vec::new();
270 let mut frame_sps: Vec<Bytes> = Vec::new();
271 let mut frame_pps: Vec<Bytes> = Vec::new();
272 let mut emitted_any_slice = false;
273
274 loop {
275 let nal = match nal_iter.next() {
276 Some(Ok(n)) => n,
277 Some(Err(e)) => return Err(e.into()),
278 None => break,
279 };
280 if process_nal(&nal, &mut out, &mut frame_vps, &mut frame_sps, &mut frame_pps)? {
281 emitted_any_slice = true;
282 }
283 }
284
285 if let Some(nal) = nal_iter.flush()?
286 && process_nal(&nal, &mut out, &mut frame_vps, &mut frame_sps, &mut frame_pps)?
287 {
288 emitted_any_slice = true;
289 }
290
291 let mut changed = false;
296 if !frame_vps.is_empty() && frame_vps != self.vps {
297 self.vps = frame_vps;
298 changed = true;
299 }
300 if !frame_sps.is_empty() && frame_sps != self.sps {
301 self.sps = frame_sps;
302 changed = true;
303 }
304 if !frame_pps.is_empty() && frame_pps != self.pps {
305 self.pps = frame_pps;
306 changed = true;
307 }
308 if changed {
309 self.rebuild_hvcc()?;
310 }
311
312 if !emitted_any_slice {
313 return Ok(None);
314 }
315
316 Ok(Some(out.freeze()))
317 }
318
319 fn rebuild_hvcc(&mut self) -> Result<()> {
320 if self.vps.is_empty() || self.sps.is_empty() || self.pps.is_empty() {
321 return Ok(());
322 }
323 self.hvcc = Some(build_hvcc(&self.vps, &self.sps, &self.pps)?);
324 Ok(())
325 }
326}
327
328fn process_nal(
332 nal: &Bytes,
333 out: &mut BytesMut,
334 frame_vps: &mut Vec<Bytes>,
335 frame_sps: &mut Vec<Bytes>,
336 frame_pps: &mut Vec<Bytes>,
337) -> Result<bool> {
338 if nal.is_empty() {
339 return Ok(false);
340 }
341 match NALUnitType::from((nal[0] >> 1) & 0x3f) {
343 NALUnitType::VpsNut => {
344 crate::codec::annexb::push_distinct(frame_vps, nal);
345 Ok(false)
346 }
347 NALUnitType::SpsNut => {
348 crate::codec::annexb::push_distinct(frame_sps, nal);
349 Ok(false)
350 }
351 NALUnitType::PpsNut => {
352 crate::codec::annexb::push_distinct(frame_pps, nal);
353 Ok(false)
354 }
355 _ => {
356 let len = u32::try_from(nal.len()).map_err(|_| Error::NalTooLarge)?;
357 out.extend_from_slice(&len.to_be_bytes());
358 out.extend_from_slice(nal);
359 Ok(true)
360 }
361 }
362}
363
364pub(crate) fn build_hvcc(vps_nals: &[Bytes], sps_nals: &[Bytes], pps_nals: &[Bytes]) -> Result<Bytes> {
369 let first_sps = sps_nals.first().ok_or(Error::MissingSps)?;
370 for (label, nals) in [("VPS", vps_nals), ("SPS", sps_nals), ("PPS", pps_nals)] {
371 if nals.len() > u16::MAX as usize {
372 return Err(Error::TooManyNals(label, nals.len()));
373 }
374 for nal in nals {
375 if nal.len() > u16::MAX as usize {
376 return Err(Error::NalTooLargeForHvcc(label, nal.len()));
377 }
378 }
379 }
380
381 let sps = SpsNALUnit::parse(&mut &first_sps[..]).map_err(|_| Error::SpsParse)?;
382 let profile = &sps.rbsp.profile_tier_level.general_profile;
383 let level_idc = profile.level_idc.ok_or(Error::MissingLevelIdc)?;
384 let constraint_flags = pack_constraint_flags(profile);
385 let compat = profile.profile_compatibility_flag.bits().to_be_bytes();
386 let num_temporal_layers = sps.rbsp.sps_max_sub_layers_minus1 + 1;
387
388 let params_len: usize = vps_nals
389 .iter()
390 .chain(sps_nals)
391 .chain(pps_nals)
392 .map(|n| 2 + n.len())
393 .sum();
394 let mut out = BytesMut::with_capacity(23 + 3 * 3 + params_len);
395 out.put_u8(1); out.put_u8(((profile.profile_space & 0x3) << 6) | ((profile.tier_flag as u8) << 5) | (profile.profile_idc & 0x1f));
397 out.put_slice(&compat);
398 out.put_slice(&constraint_flags);
399 out.put_u8(level_idc);
400 out.put_u16(0xf000); out.put_u8(0xfc); out.put_u8(0xfc | (sps.rbsp.chroma_format_idc & 0x3));
403 out.put_u8(0xf8 | (sps.rbsp.bit_depth_luma_minus8 & 0x7));
404 out.put_u8(0xf8 | (sps.rbsp.bit_depth_chroma_minus8 & 0x7));
405 out.put_u16(0); out.put_u8(((num_temporal_layers & 0x7) << 3) | ((sps.rbsp.sps_temporal_id_nesting_flag as u8) << 2) | 0x3);
407 out.put_u8(3); for (nal_type, nals) in [
410 (u8::from(NALUnitType::VpsNut), vps_nals),
411 (u8::from(NALUnitType::SpsNut), sps_nals),
412 (u8::from(NALUnitType::PpsNut), pps_nals),
413 ] {
414 out.put_u8(0x80 | (nal_type & 0x3f)); out.put_u16(nals.len() as u16); for nal in nals {
417 out.put_u16(nal.len() as u16);
418 out.put_slice(nal);
419 }
420 }
421
422 Ok(out.freeze())
423}
424
425pub(crate) fn hvcc_params(hvcc: &[u8]) -> anyhow::Result<(usize, Vec<Bytes>)> {
430 anyhow::ensure!(hvcc.len() >= 23, "HEVCDecoderConfigurationRecord too short");
431 let length_size = (hvcc[21] & 0x03) as usize + 1;
432 let num_arrays = hvcc[22];
433
434 let mut params = Vec::new();
435 let mut pos = 23;
436 for _ in 0..num_arrays {
437 anyhow::ensure!(hvcc.len() >= pos + 3, "truncated hvcC NAL array header");
439 pos += 1;
440 let num_nalus = u16::from_be_bytes([hvcc[pos], hvcc[pos + 1]]);
441 pos += 2;
442 for _ in 0..num_nalus {
443 anyhow::ensure!(hvcc.len() >= pos + 2, "truncated hvcC NAL length");
444 let len = u16::from_be_bytes([hvcc[pos], hvcc[pos + 1]]) as usize;
445 pos += 2;
446 anyhow::ensure!(hvcc.len() >= pos + len, "hvcC NAL exceeds buffer");
447 params.push(Bytes::copy_from_slice(&hvcc[pos..pos + len]));
448 pos += len;
449 }
450 }
451
452 Ok((length_size, params))
453}
454
455pub(crate) fn pack_constraint_flags(profile: &scuffle_h265::Profile) -> [u8; 6] {
457 let mut flags = [0u8; 6];
458 flags[0] = ((profile.progressive_source_flag as u8) << 7)
459 | ((profile.interlaced_source_flag as u8) << 6)
460 | ((profile.non_packed_constraint_flag as u8) << 5)
461 | ((profile.frame_only_constraint_flag as u8) << 4);
462 flags
463}
464
465#[cfg(test)]
468pub(crate) mod fixtures {
469 use bytes::Bytes;
470
471 pub(crate) const VPS: &[u8] = &[
472 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00,
473 0x03, 0x00, 0x5d, 0x95, 0x98, 0x09,
474 ];
475 pub(crate) const SPS: &[u8] = &[
476 0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x5d,
477 0xa0, 0x02, 0x80, 0x80, 0x2d, 0x16, 0x59, 0x59, 0xa4, 0x93, 0x2b, 0xc0, 0x5a, 0x02, 0x00, 0x00, 0x03, 0x00,
478 0x02, 0x00, 0x00, 0x03, 0x00, 0x3c, 0x10,
479 ];
480 pub(crate) const PPS: &[u8] = &[0x44, 0x01, 0xc1, 0x72, 0xb4, 0x62, 0x40];
481
482 pub(crate) fn hvcc() -> Bytes {
484 super::build_hvcc(
485 &[Bytes::from_static(VPS)],
486 &[Bytes::from_static(SPS)],
487 &[Bytes::from_static(PPS)],
488 )
489 .expect("real parameter sets must build an hvcC")
490 }
491}
492
493#[cfg(test)]
494mod tests {
495 use super::*;
496
497 fn length_prefixed(nals: &[&[u8]]) -> Vec<u8> {
499 let mut au = Vec::new();
500 for nal in nals {
501 au.extend_from_slice(&(nal.len() as u32).to_be_bytes());
502 au.extend_from_slice(nal);
503 }
504 au
505 }
506
507 fn ts() -> moq_net::Timestamp {
508 moq_net::Timestamp::from_micros(0).unwrap()
509 }
510
511 #[test]
515 fn hvc1_frame_keyframe() {
516 let sei: &[u8] = &[0x4e, 0x01, 0x05, 0xff]; let idr: &[u8] = &[0x26, 0x01, 0x80, 0xaa]; let au = length_prefixed(&[sei, idr]);
519
520 let frame = hvc1_frame(&au, 4, ts()).unwrap();
521 assert!(frame.keyframe);
522 assert_eq!(frame.payload, au);
523 }
524
525 #[test]
527 fn hvc1_frame_cra_keyframe() {
528 let cra: &[u8] = &[0x2a, 0x01, 0x80, 0x55]; let au = length_prefixed(&[cra]);
530
531 let frame = hvc1_frame(&au, 4, ts()).unwrap();
532 assert!(frame.keyframe);
533 }
534
535 #[test]
537 fn hvc1_frame_delta() {
538 let trail: &[u8] = &[0x02, 0x01, 0x80, 0x33]; let au = length_prefixed(&[trail]);
540
541 let frame = hvc1_frame(&au, 4, ts()).unwrap();
542 assert!(!frame.keyframe);
543 }
544
545 #[test]
549 fn hvc1_frame_tsa_delta_not_h264_idr() {
550 let tsa: &[u8] = &[0x05, 0x01, 0x80, 0x33]; let au = length_prefixed(&[tsa]);
552
553 let frame = hvc1_frame(&au, 4, ts()).unwrap();
554 assert!(!frame.keyframe);
555 }
556
557 #[test]
560 fn config_from_hvcc_resolves_real_sps() {
561 let hvcc = fixtures::hvcc();
562 let config = config_from_hvcc(&hvcc).unwrap();
563
564 let hang::catalog::VideoCodec::H265(h265) = &config.codec else {
565 panic!("expected H.265 codec")
566 };
567 assert!(!h265.in_band, "hvcC config is out-of-band");
568 assert_eq!(config.coded_width, Some(1280));
569 assert_eq!(config.coded_height, Some(720));
570 assert_eq!(config.description.as_deref(), Some(hvcc.as_ref()));
571 }
572
573 #[test]
577 fn hvcc_params_parses_vps_sps_pps() {
578 let vps = &[0x40, 0x01, 0x0c][..]; let sps = &[0x42, 0x01, 0x01, 0x60][..]; let pps = &[0x44, 0x01, 0xc0][..]; let mut hvcc = BytesMut::new();
583 hvcc.extend_from_slice(&[0u8; 21]); hvcc.put_u8(0xfc | 0x03); hvcc.put_u8(3); for (nal_type, nal) in [
587 (u8::from(NALUnitType::VpsNut), vps),
588 (u8::from(NALUnitType::SpsNut), sps),
589 (u8::from(NALUnitType::PpsNut), pps),
590 ] {
591 hvcc.put_u8(0x80 | (nal_type & 0x3f));
592 hvcc.put_u16(1); hvcc.put_u16(nal.len() as u16);
594 hvcc.put_slice(nal);
595 }
596
597 let (length_size, params) = hvcc_params(&hvcc).unwrap();
598 assert_eq!(length_size, 4);
599 assert_eq!(params.len(), 3);
600 assert_eq!(params[0].as_ref(), vps);
601 assert_eq!(params[1].as_ref(), sps);
602 assert_eq!(params[2].as_ref(), pps);
603 }
604}