use bytes::{Buf, Bytes, BytesMut};
use scuffle_av1::{ObuHeader, ObuType};
use super::Error;
use crate::Result;
pub struct Split {
current: Au,
zero: Option<tokio::time::Instant>,
pending: Vec<crate::container::Frame>,
tail: BytesMut,
}
#[derive(Default)]
struct Au {
chunks: BytesMut,
contains_keyframe: bool,
contains_frame: bool,
}
impl Default for Split {
fn default() -> Self {
Self::new()
}
}
impl Split {
pub fn new() -> Self {
Self {
current: Au::default(),
zero: None,
pending: Vec::new(),
tail: BytesMut::new(),
}
}
pub fn decode(
&mut self,
data: &[u8],
pts: impl Into<Option<crate::container::Timestamp>>,
) -> Result<Vec<crate::container::Frame>> {
let hint = pts.into();
self.tail.extend_from_slice(data);
let obus = {
let it = ObuIterator::new(&mut self.tail);
it.collect::<Result<Vec<_>>>()?
};
for obu in obus {
let pts = self.pts(hint)?;
self.decode_obu(obu, Some(pts))?;
}
Ok(std::mem::take(&mut self.pending))
}
pub fn flush(
&mut self,
pts: impl Into<Option<crate::container::Timestamp>>,
) -> Result<Vec<crate::container::Frame>> {
let pts = self.pts(pts.into())?;
self.maybe_start_frame(Some(pts))?;
Ok(std::mem::take(&mut self.pending))
}
fn decode_obu(&mut self, obu_data: Bytes, pts: Option<crate::container::Timestamp>) -> Result<()> {
if obu_data.is_empty() {
return Err(Error::ObuTooShort.into());
}
let mut reader = &obu_data[..];
let header = ObuHeader::parse(&mut reader)?;
let payload_offset = obu_data.len() - reader.len();
match header.obu_type {
ObuType::SequenceHeader => {
self.current.contains_keyframe = true;
}
ObuType::TemporalDelimiter => {
self.maybe_start_frame(pts)?;
}
ObuType::FrameHeader | ObuType::Frame => {
let is_keyframe = obu_data.get(payload_offset).is_some_and(|first_byte| {
let show_existing_frame = (first_byte >> 7) & 1;
if show_existing_frame == 1 {
self.current.contains_keyframe
} else {
let frame_type = (first_byte >> 5) & 0b11;
frame_type == 0 }
});
if is_keyframe {
self.current.contains_keyframe = true;
}
self.current.contains_frame = true;
}
ObuType::Metadata => {
self.maybe_start_frame(pts)?;
}
ObuType::TileGroup | ObuType::TileList => {
self.current.contains_frame = true;
}
_ => {}
}
tracing::trace!(?header.obu_type, "parsed OBU");
self.current.chunks.extend_from_slice(&obu_data);
Ok(())
}
fn maybe_start_frame(&mut self, pts: Option<crate::container::Timestamp>) -> Result<()> {
if !self.current.contains_frame {
return Ok(());
}
let pts = pts.ok_or(Error::MissingTimestamp)?;
let keyframe = self.current.contains_keyframe;
let payload = std::mem::take(&mut self.current.chunks).freeze();
self.current.contains_keyframe = false;
self.current.contains_frame = false;
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();
}
fn pts(&mut self, hint: Option<crate::container::Timestamp>) -> Result<crate::container::Timestamp> {
if let Some(pts) = hint {
return Ok(pts);
}
let zero = self.zero.get_or_insert_with(tokio::time::Instant::now);
Ok(crate::container::Timestamp::from_micros(
zero.elapsed().as_micros() as u64
)?)
}
}
pub(super) struct ObuIterator<'a, T: Buf + AsRef<[u8]> + 'a> {
buf: &'a mut T,
}
impl<'a, T: Buf + AsRef<[u8]> + 'a> ObuIterator<'a, T> {
pub fn new(buf: &'a mut T) -> Self {
Self { buf }
}
pub fn flush(self) -> Result<Option<Bytes>> {
let remaining = self.buf.remaining();
if remaining == 0 {
return Ok(None);
}
Ok(Some(self.buf.copy_to_bytes(remaining)))
}
}
impl<'a, T: Buf + AsRef<[u8]> + 'a> Iterator for ObuIterator<'a, T> {
type Item = Result<Bytes>;
fn next(&mut self) -> Option<Self::Item> {
if self.buf.remaining() == 0 {
return None;
}
let data = self.buf.as_ref();
if data.is_empty() {
return None;
}
let header = data[0];
let has_extension = (header >> 2) & 1 == 1;
let has_size = (header >> 1) & 1 == 1;
if !has_size {
let remaining = self.buf.remaining();
return Some(Ok(self.buf.copy_to_bytes(remaining)));
}
let mut size: usize = 0;
let mut offset = if has_extension { 2 } else { 1 };
let mut shift = 0;
loop {
if offset >= data.len() {
return None;
}
let byte = data[offset];
offset += 1;
size |= ((byte & 0x7F) as usize) << shift;
shift += 7;
if byte & 0x80 == 0 {
break;
}
if shift >= 56 {
return Some(Err(Error::ObuSizeTooLarge.into()));
}
}
let total_size = offset + size;
if total_size > self.buf.remaining() {
return None;
}
Some(Ok(self.buf.copy_to_bytes(total_size)))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn obu(obu_type: u8, payload: &[u8]) -> Vec<u8> {
let mut o = vec![(obu_type << 3) | 0b010, payload.len() as u8];
o.extend_from_slice(payload);
o
}
fn td() -> Vec<u8> {
obu(2, &[]) }
fn seq_header() -> Vec<u8> {
obu(1, &[0xaa, 0xbb]) }
fn key_frame() -> Vec<u8> {
obu(6, &[0x00, 0x11]) }
fn inter_frame() -> Vec<u8> {
obu(6, &[0x20, 0x11]) }
fn cat(parts: &[Vec<u8>]) -> BytesMut {
let mut buf = BytesMut::new();
for p in parts {
buf.extend_from_slice(p);
}
buf
}
fn ts() -> crate::container::Timestamp {
crate::container::Timestamp::from_micros(0).unwrap()
}
fn decode_one(
split: &mut Split,
buf: &mut BytesMut,
pts: crate::container::Timestamp,
) -> Vec<crate::container::Frame> {
let mut frames = split.decode(buf, Some(pts)).unwrap();
frames.extend(split.flush(Some(pts)).unwrap());
frames
}
#[tokio::test(start_paused = true)]
async fn decode_keyframe() {
let mut split = Split::new();
let frames = decode_one(&mut split, &mut cat(&[td(), seq_header(), key_frame()]), ts());
assert_eq!(frames.len(), 1);
assert!(frames[0].keyframe);
}
#[tokio::test(start_paused = true)]
async fn decode_delta_is_not_keyframe() {
let mut split = Split::new();
let frames = decode_one(&mut split, &mut cat(&[td(), inter_frame()]), ts());
assert_eq!(frames.len(), 1);
assert!(!frames[0].keyframe);
}
#[tokio::test(start_paused = true)]
async fn decode_emits_on_next_boundary() {
let mut split = Split::new();
let frames = split
.decode(
&cat(&[td(), seq_header(), key_frame(), td(), inter_frame()]),
Some(ts()),
)
.unwrap();
assert_eq!(frames.len(), 1);
assert!(frames[0].keyframe);
let tail = split.flush(Some(ts())).unwrap();
assert_eq!(tail.len(), 1);
assert!(!tail[0].keyframe);
}
}