use super::bits::BitReader;
use super::frame::FrameHeader;
use super::obu::{Obu, ObuType};
use super::seq::SequenceHeader;
use core::ops::Range;
use otf_pixels_core::{PixelsError, Result};
#[derive(Debug, Clone)]
pub struct StillPicture {
pub sequence: SequenceHeader,
pub frame: FrameHeader,
pub tile_groups: Vec<Range<usize>>,
}
impl StillPicture {
pub fn tile_group_data<'a>(&self, frame_data: &'a [u8]) -> Result<Vec<&'a [u8]>> {
self.tile_groups
.iter()
.map(|range| {
frame_data.get(range.clone()).ok_or_else(|| {
PixelsError::malformed("avif", "tile group lies outside the coded frame")
})
})
.collect()
}
}
pub fn sequence_header_from_config(config_obus: &[u8]) -> Result<SequenceHeader> {
for obu in Obu::parse_stream(config_obus)? {
if obu.header.kind == ObuType::SequenceHeader {
let mut reader = BitReader::new(obu.payload);
return SequenceHeader::parse(&mut reader);
}
}
Err(PixelsError::malformed(
"avif",
"the av1C configuration OBUs contain no sequence header",
))
}
impl StillPicture {
pub fn parse(config_obus: &[u8], frame_data: &[u8]) -> Result<Self> {
let mut sequence = sequence_header_from_config(config_obus).ok();
let obus = Obu::parse_stream(frame_data)?;
let mut frame = None;
let mut tile_groups = Vec::new();
for obu in &obus {
let payload_offset = obu.payload_start;
match obu.header.kind {
ObuType::SequenceHeader => {
let mut reader = BitReader::new(obu.payload);
sequence = Some(SequenceHeader::parse(&mut reader)?);
}
ObuType::Frame => {
let seq = sequence.as_ref().ok_or_else(missing_sequence)?;
let mut reader = BitReader::new(obu.payload);
let header = FrameHeader::parse(
&mut reader,
seq,
obu.header.temporal_id,
obu.header.spatial_id,
)?;
reader.byte_alignment()?;
let consumed = reader.byte_position().min(obu.payload.len());
frame = Some(header);
tile_groups.clear();
tile_groups.push(payload_offset + consumed..payload_offset + obu.payload.len());
}
ObuType::RedundantFrameHeader if frame.is_some() => {}
ObuType::FrameHeader | ObuType::RedundantFrameHeader => {
let seq = sequence.as_ref().ok_or_else(missing_sequence)?;
let mut reader = BitReader::new(obu.payload);
let header = FrameHeader::parse(
&mut reader,
seq,
obu.header.temporal_id,
obu.header.spatial_id,
)?;
frame = Some(header);
tile_groups.clear();
}
ObuType::TileGroup if frame.is_some() => {
tile_groups.push(payload_offset..payload_offset + obu.payload.len());
}
_ => {}
}
}
let sequence = sequence.ok_or_else(missing_sequence)?;
let frame = frame.ok_or_else(|| {
PixelsError::malformed("avif", "the coded frame contains no frame header")
})?;
if tile_groups.is_empty() {
return Err(PixelsError::malformed(
"avif",
"the coded frame has a header but no tile group",
));
}
Ok(Self {
sequence,
frame,
tile_groups,
})
}
}
fn missing_sequence() -> PixelsError {
PixelsError::malformed(
"avif",
"a frame header was reached before any sequence header",
)
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::indexing_slicing,
clippy::panic,
reason = "tests operate on known-good values and assert shapes directly"
)]
mod tests {
use super::*;
struct Bldr {
bits: Vec<u8>,
}
impl Bldr {
fn new() -> Self {
Self { bits: Vec::new() }
}
fn put(&mut self, value: u32, n: u32) -> &mut Self {
for i in (0..n).rev() {
self.bits.push(((value >> i) & 1) as u8);
}
self
}
fn flag(&mut self, b: bool) -> &mut Self {
self.put(u32::from(b), 1)
}
fn pack(&self) -> Vec<u8> {
let mut out = vec![0_u8; self.bits.len().div_ceil(8)];
for (i, &bit) in self.bits.iter().enumerate() {
if bit != 0 {
out[i / 8] |= 1 << (7 - (i % 8));
}
}
out
}
}
fn obu(kind: u8, payload: &[u8]) -> Vec<u8> {
let mut out = vec![(kind << 3) | 0b0000_0010];
assert!(payload.len() < 128);
out.push(payload.len() as u8);
out.extend_from_slice(payload);
out
}
fn seq_bytes(width: u32, height: u32) -> Vec<u8> {
let mut b = Bldr::new();
b.put(0, 3).flag(true).flag(true).put(1, 5);
b.put(15, 4)
.put(15, 4)
.put(width - 1, 16)
.put(height - 1, 16);
b.flag(false).flag(false).flag(false);
b.flag(false).flag(false).flag(false);
b.flag(false).flag(false).flag(false);
b.flag(false).put(0, 2).flag(false);
b.flag(false);
b.pack()
}
fn frame_bytes(tile: &[u8]) -> Vec<u8> {
let mut b = Bldr::new();
b.flag(false); b.flag(false); b.flag(false); b.flag(true); b.put(100, 8); b.flag(false).flag(false).flag(false).flag(false); b.flag(false); b.flag(false); b.put(0, 6).put(0, 6).put(0, 3).flag(false); b.flag(false); b.flag(false); let mut bytes = b.pack();
bytes.extend_from_slice(tile);
bytes
}
#[test]
fn parses_config_and_frame_into_headers_and_tile_locator() {
let config = obu(1, &seq_bytes(64, 64)); let tile = [0xDE, 0xAD, 0xBE, 0xEF];
let frame_payload = frame_bytes(&tile);
let frame_stream = obu(6, &frame_payload);
let still = StillPicture::parse(&config, &frame_stream).unwrap();
assert_eq!(still.sequence.max_frame_width, 64);
assert_eq!(still.frame.frame_width, 64);
assert_eq!(still.frame.quantization.base_q_idx, 100);
assert_eq!(still.frame.tile_info.count(), 1);
let located = still.tile_group_data(&frame_stream).unwrap();
assert_eq!(located, [&tile[..]]);
}
#[test]
fn a_separate_frame_header_and_tile_group_are_joined() {
let config = obu(1, &seq_bytes(32, 32));
let header_only = frame_bytes(&[]);
let mut stream = obu(3, &header_only); let tile = [1, 2, 3];
stream.extend(obu(4, &tile)); stream.extend(obu(4, &[4, 5]));
let still = StillPicture::parse(&config, &stream).unwrap();
assert_eq!(still.frame.frame_width, 32);
let located = still.tile_group_data(&stream).unwrap();
assert_eq!(located, [&tile[..], &[4, 5][..]]);
}
#[test]
fn a_redundant_frame_header_keeps_the_tile_groups_already_seen() {
let config = obu(1, &seq_bytes(32, 32));
let header = frame_bytes(&[]);
let mut stream = obu(3, &header); stream.extend(obu(4, &[1, 2])); stream.extend(obu(7, &header)); stream.extend(obu(4, &[3]));
let still = StillPicture::parse(&config, &stream).unwrap();
let located = still.tile_group_data(&stream).unwrap();
assert_eq!(located, [&[1, 2][..], &[3][..]]);
}
#[test]
fn a_frame_header_without_tile_data_is_malformed() {
let config = obu(1, &seq_bytes(32, 32));
let stream = obu(3, &frame_bytes(&[])); let error = StillPicture::parse(&config, &stream).unwrap_err();
assert!(error.to_string().contains("no tile group"), "{error}");
}
#[test]
fn an_in_band_sequence_header_overrides_the_config_one() {
let config = obu(1, &seq_bytes(64, 64));
let mut stream = obu(1, &seq_bytes(40, 40));
stream.extend(obu(6, &frame_bytes(&[0x00])));
let still = StillPicture::parse(&config, &stream).unwrap();
assert_eq!(still.sequence.max_frame_width, 40);
assert_eq!(still.frame.frame_width, 40);
}
#[test]
fn a_frame_with_no_sequence_header_anywhere_is_rejected() {
let stream = obu(6, &frame_bytes(&[0x00]));
let err = StillPicture::parse(&[], &stream).unwrap_err();
assert!(err.to_string().contains("sequence header"), "{err}");
}
#[test]
fn config_without_a_sequence_header_is_reported() {
let config = obu(2, &[]); let err = sequence_header_from_config(&config).unwrap_err();
assert!(err.to_string().contains("no sequence header"), "{err}");
}
}