use bytes::{Bytes, BytesMut};
use scuffle_h265::NALUnitType;
use super::Error;
use crate::Result;
use crate::codec::annexb::{NalIterator, START_CODE};
pub struct Split {
tail: BytesMut,
current: Au,
vps: Vec<Bytes>,
sps: Vec<Bytes>,
pps: Vec<Bytes>,
zero: Option<web_async::time::Instant>,
pending: Vec<crate::container::Frame>,
}
pub(crate) struct Params {
vps: Vec<Bytes>,
sps: Vec<Bytes>,
pps: Vec<Bytes>,
}
#[derive(Default)]
struct Au {
chunks: BytesMut,
contains_idr: bool,
contains_slice: bool,
vps_seen: Vec<Bytes>,
sps_seen: Vec<Bytes>,
pps_seen: Vec<Bytes>,
}
impl Default for Split {
fn default() -> Self {
Self::new()
}
}
impl Split {
pub fn new() -> Self {
Self {
tail: BytesMut::new(),
current: Au::default(),
vps: Vec::new(),
sps: Vec::new(),
pps: Vec::new(),
zero: None,
pending: Vec::new(),
}
}
pub fn decode(
&mut self,
data: &[u8],
pts: impl Into<Option<moq_net::Timestamp>>,
) -> Result<Vec<crate::container::Frame>> {
let pts = self.pts(pts.into())?;
self.tail.extend_from_slice(data);
let nals = NalIterator::new(&mut self.tail);
let mut parsed = Vec::new();
for nal in nals {
parsed.push(nal?);
}
for nal in parsed {
self.decode_nal(nal, pts)?;
}
Ok(std::mem::take(&mut self.pending))
}
pub fn flush(&mut self, pts: impl Into<Option<moq_net::Timestamp>>) -> Result<Vec<crate::container::Frame>> {
let pts = self.pts(pts.into())?;
if let Some(nal) = NalIterator::new(&mut self.tail).flush()? {
self.decode_nal(nal, pts)?;
}
self.tail.clear();
self.maybe_start_frame(pts)?;
Ok(std::mem::take(&mut self.pending))
}
fn decode_nal(&mut self, nal: Bytes, pts: moq_net::Timestamp) -> Result<()> {
if nal.len() < 2 {
return Err(Error::NalTooShort.into());
}
let header = nal.first().ok_or(Error::NalTooShort)?;
if (header >> 7) & 1 != 0 {
return Err(Error::ForbiddenZeroBit.into());
}
let nal_type = nal_unit_type(*header);
match nal_type {
NALUnitType::VpsNut => {
self.maybe_start_frame(pts)?;
crate::codec::annexb::push_distinct(&mut self.current.vps_seen, &nal);
}
NALUnitType::SpsNut => {
self.maybe_start_frame(pts)?;
crate::codec::annexb::push_distinct(&mut self.current.sps_seen, &nal);
}
NALUnitType::PpsNut => {
self.maybe_start_frame(pts)?;
crate::codec::annexb::push_distinct(&mut self.current.pps_seen, &nal);
}
NALUnitType::AudNut | NALUnitType::PrefixSeiNut => {
self.maybe_start_frame(pts)?;
}
NALUnitType::SuffixSeiNut => {}
nal_type if super::is_irap(nal_type) => {
if nal.get(2).ok_or(Error::NalTooShort)? & 0x80 != 0 {
self.maybe_start_frame(pts)?;
}
crate::codec::annexb::reconcile_keyframe_params(
&mut self.current.chunks,
&mut self.vps,
&mut self.current.vps_seen,
);
crate::codec::annexb::reconcile_keyframe_params(
&mut self.current.chunks,
&mut self.sps,
&mut self.current.sps_seen,
);
crate::codec::annexb::reconcile_keyframe_params(
&mut self.current.chunks,
&mut self.pps,
&mut self.current.pps_seen,
);
self.current.contains_idr = true;
self.current.contains_slice = true;
}
NALUnitType::TrailN
| NALUnitType::TrailR
| NALUnitType::TsaN
| NALUnitType::TsaR
| NALUnitType::StsaN
| NALUnitType::StsaR
| NALUnitType::RadlN
| NALUnitType::RadlR
| NALUnitType::RaslN
| NALUnitType::RaslR => {
if nal.get(2).ok_or(Error::NalTooShort)? & 0x80 != 0 {
self.maybe_start_frame(pts)?;
}
self.current.contains_slice = true;
}
_ => {}
}
self.current.chunks.extend_from_slice(&START_CODE);
self.current.chunks.extend_from_slice(&nal);
Ok(())
}
fn maybe_start_frame(&mut self, pts: moq_net::Timestamp) -> Result<()> {
if !self.current.contains_slice {
return Ok(());
}
let payload = std::mem::take(&mut self.current.chunks).freeze();
let keyframe = self.current.contains_idr;
self.current.contains_idr = false;
self.current.contains_slice = false;
self.current.vps_seen.clear();
self.current.sps_seen.clear();
self.current.pps_seen.clear();
self.pending.push(crate::container::Frame {
timestamp: pts,
payload,
keyframe,
duration: None,
});
Ok(())
}
pub fn reset(&mut self) {
self.current = Au::default();
self.tail.clear();
self.pending.clear();
}
pub(crate) fn params(&self) -> Params {
Params {
vps: self.vps.clone(),
sps: self.sps.clone(),
pps: self.pps.clone(),
}
}
pub(crate) fn restore(&mut self, params: Params) {
self.vps = params.vps;
self.sps = params.sps;
self.pps = params.pps;
}
fn pts(&mut self, hint: Option<moq_net::Timestamp>) -> Result<moq_net::Timestamp> {
if let Some(pts) = hint {
return Ok(pts);
}
let zero = self.zero.get_or_insert_with(web_async::time::Instant::now);
Ok(moq_net::Timestamp::from_micros(zero.elapsed().as_micros() as u64)?)
}
}
pub(super) fn nal_unit_type(header: u8) -> NALUnitType {
NALUnitType::from((header >> 1) & 0b111111)
}
#[cfg(test)]
mod tests {
use super::*;
const SC4: &[u8] = &[0, 0, 0, 1];
const VPS: &[u8] = &[0x40, 0x01, 0x0c]; const SPS: &[u8] = &[0x42, 0x01, 0x01]; const PPS: &[u8] = &[0x44, 0x01, 0xc0]; const IDR: &[u8] = &[0x26, 0x01, 0x80, 0xaa]; const TRAIL: &[u8] = &[0x02, 0x01, 0x80, 0x33];
const AUD: &[u8] = &[0x46, 0x01, 0x50]; const PREFIX_SEI: &[u8] = &[0x4e, 0x01, 0x01, 0x04, 0x80]; const SUFFIX_SEI: &[u8] = &[0x50, 0x01, 0x84, 0x02, 0x80]; const SUFFIX_SEI2: &[u8] = &[0x50, 0x01, 0x05, 0x03, 0x80];
fn annexb(nals: &[&[u8]]) -> BytesMut {
let mut buf = BytesMut::new();
for nal in nals {
buf.extend_from_slice(SC4);
buf.extend_from_slice(nal);
}
buf
}
fn ts() -> moq_net::Timestamp {
moq_net::Timestamp::from_micros(0).unwrap()
}
fn contains(haystack: &[u8], needle: &[u8]) -> bool {
haystack.windows(needle.len()).any(|w| w == needle)
}
fn decode_one(split: &mut Split, buf: &mut BytesMut, pts: moq_net::Timestamp) -> Vec<crate::container::Frame> {
let mut frames = split.decode(buf, pts).unwrap();
frames.extend(split.flush(pts).unwrap());
frames
}
fn length_prefixed(nals: &[&[u8]]) -> Bytes {
let mut buf = BytesMut::new();
for nal in nals {
buf.extend_from_slice(&(nal.len() as u32).to_be_bytes());
buf.extend_from_slice(nal);
}
buf.freeze()
}
fn is_param_set(nal: &[u8]) -> bool {
matches!(
nal_unit_type(nal[0]),
NALUnitType::VpsNut | NALUnitType::SpsNut | NALUnitType::PpsNut
)
}
type SeiCase = (&'static str, Vec<&'static [u8]>, Vec<Vec<&'static [u8]>>);
fn suffix_sei_cases() -> Vec<SeiCase> {
use crate::codec::h265::fixtures::{PPS, SPS, VPS};
vec![
(
"consecutive pictures",
vec![VPS, SPS, PPS, IDR, SUFFIX_SEI, AUD, TRAIL, SUFFIX_SEI],
vec![vec![VPS, SPS, PPS, IDR, SUFFIX_SEI], vec![AUD, TRAIL, SUFFIX_SEI]],
),
(
"multiple suffix units",
vec![VPS, SPS, PPS, IDR, SUFFIX_SEI, SUFFIX_SEI2, AUD, TRAIL],
vec![vec![VPS, SPS, PPS, IDR, SUFFIX_SEI, SUFFIX_SEI2], vec![AUD, TRAIL]],
),
(
"suffix at EOF",
vec![VPS, SPS, PPS, IDR, AUD, TRAIL, SUFFIX_SEI],
vec![vec![VPS, SPS, PPS, IDR], vec![AUD, TRAIL, SUFFIX_SEI]],
),
(
"prefix immediately after suffix",
vec![VPS, SPS, PPS, IDR, SUFFIX_SEI, PREFIX_SEI, TRAIL],
vec![vec![VPS, SPS, PPS, IDR, SUFFIX_SEI], vec![PREFIX_SEI, TRAIL]],
),
]
}
#[tokio::test(start_paused = true)]
async fn annexb_suffix_sei_stays_with_its_picture() {
for (name, stream, expected) in suffix_sei_cases() {
let mut split = Split::new();
let frames = decode_one(&mut split, &mut annexb(&stream), ts());
assert_eq!(frames.len(), expected.len(), "{name}: access unit count");
for (i, (frame, nals)) in frames.iter().zip(&expected).enumerate() {
assert_eq!(
frame.payload.as_ref(),
annexb(nals).freeze().as_ref(),
"{name}: access unit {i}"
);
}
assert!(frames[0].keyframe, "{name}: the first access unit is the keyframe");
assert!(!frames[1].keyframe, "{name}: the second is a delta picture");
}
}
#[tokio::test(start_paused = true)]
async fn length_prefixed_suffix_sei_stays_with_its_picture() {
for (name, stream, expected) in suffix_sei_cases() {
let mut split = Split::new();
let frames = decode_one(&mut split, &mut annexb(&stream), ts());
let mut hvc1 = crate::codec::h265::Hvc1::new();
assert_eq!(frames.len(), expected.len(), "{name}: access unit count");
for (i, (frame, nals)) in frames.iter().zip(&expected).enumerate() {
let sample = hvc1
.transform(frame.payload.clone())
.unwrap()
.expect("an access unit with slice data");
let want: Vec<&[u8]> = nals.iter().copied().filter(|nal| !is_param_set(nal)).collect();
assert_eq!(sample.as_ref(), length_prefixed(&want).as_ref(), "{name}: sample {i}");
}
assert!(
hvc1.hvcc().is_some(),
"{name}: the keyframe's parameter sets build an hvcC"
);
}
}
#[tokio::test(start_paused = true)]
async fn decode_packages_keyframe() {
let mut split = Split::new();
let frames = decode_one(&mut split, &mut annexb(&[VPS, SPS, PPS, IDR]), ts());
assert_eq!(frames.len(), 1);
assert!(frames[0].keyframe);
assert!(contains(&frames[0].payload, VPS));
assert!(contains(&frames[0].payload, SPS));
assert!(contains(&frames[0].payload, PPS));
assert!(contains(&frames[0].payload, IDR));
}
#[tokio::test(start_paused = true)]
async fn params_then_bare_keyframe_self_contained() {
let mut split = Split::new();
assert!(split.decode(&annexb(&[VPS, SPS, PPS]), ts()).unwrap().is_empty());
let frames = decode_one(&mut split, &mut annexb(&[IDR]), ts());
assert_eq!(frames.len(), 1);
assert!(frames[0].keyframe);
assert!(contains(&frames[0].payload, VPS));
assert!(contains(&frames[0].payload, SPS));
assert!(contains(&frames[0].payload, PPS));
}
#[tokio::test(start_paused = true)]
async fn reinjects_all_cached_pps_on_keyframe() {
const PPS1: &[u8] = &[0x44, 0x01, 0xc1];
let mut split = Split::new();
let first = decode_one(&mut split, &mut annexb(&[VPS, SPS, PPS, PPS1, IDR]), ts());
assert_eq!(first.len(), 1);
assert!(first[0].keyframe);
let second = decode_one(&mut split, &mut annexb(&[IDR]), ts());
assert_eq!(second.len(), 1);
assert!(second[0].keyframe);
assert_eq!(
second[0].payload.as_ref(),
annexb(&[VPS, SPS, PPS, PPS1, IDR]).freeze().as_ref()
);
}
#[tokio::test(start_paused = true)]
async fn bare_idr_after_delta_splits() {
let mut split = Split::new();
let frames = split
.decode(&annexb(&[VPS, SPS, PPS, IDR, TRAIL, IDR, AUD]), ts())
.unwrap();
assert_eq!(frames.len(), 2);
assert!(frames[0].keyframe, "first AU is the keyframe");
assert!(!frames[1].keyframe, "the delta picture must not be flagged a keyframe");
assert_eq!(frames[1].payload.as_ref(), annexb(&[TRAIL]).freeze().as_ref());
let tail = split.flush(ts()).unwrap();
assert_eq!(tail.len(), 1);
assert!(tail[0].keyframe);
assert_eq!(
tail[0].payload.as_ref(),
annexb(&[VPS, SPS, PPS, IDR]).freeze().as_ref()
);
}
#[tokio::test(start_paused = true)]
async fn reinit_drops_superseded_pps_on_keyframe() {
const PPS1: &[u8] = &[0x44, 0x01, 0xc1];
let mut split = Split::new();
let _ = decode_one(&mut split, &mut annexb(&[VPS, SPS, PPS, PPS1, IDR]), ts());
let _ = decode_one(&mut split, &mut annexb(&[VPS, SPS, PPS, IDR]), ts());
let third = decode_one(&mut split, &mut annexb(&[IDR]), ts());
assert_eq!(third.len(), 1);
assert!(third[0].keyframe);
assert_eq!(
third[0].payload.as_ref(),
annexb(&[VPS, SPS, PPS, IDR]).freeze().as_ref()
);
}
}