use crate::error::{CodecError, CodecResult};
pub const AV1_OBU_TYPE_METADATA: u8 = 5;
pub const VP8_USER_DATA_MARKER: u8 = 0xFE;
pub const UUID_LEN: usize = 16;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum SeiPayloadType {
BufferingPeriod = 0,
PictureTiming = 1,
PanScanRect = 2,
UserDataRegistered = 4,
UserDataUnregistered = 5,
RecoveryPoint = 6,
FramePacking = 45,
DisplayOrientation = 47,
Unknown = 255,
}
impl SeiPayloadType {
pub fn from_byte(b: u8) -> Self {
match b {
0 => Self::BufferingPeriod,
1 => Self::PictureTiming,
2 => Self::PanScanRect,
4 => Self::UserDataRegistered,
5 => Self::UserDataUnregistered,
6 => Self::RecoveryPoint,
45 => Self::FramePacking,
47 => Self::DisplayOrientation,
_ => Self::Unknown,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UserDataUnregistered {
pub uuid: [u8; UUID_LEN],
pub data: Vec<u8>,
}
impl UserDataUnregistered {
pub fn new(uuid: [u8; UUID_LEN], data: Vec<u8>) -> Self {
Self { uuid, data }
}
pub fn with_nil_uuid(data: Vec<u8>) -> Self {
Self::new([0u8; UUID_LEN], data)
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(UUID_LEN + self.data.len());
out.extend_from_slice(&self.uuid);
out.extend_from_slice(&self.data);
out
}
pub fn from_bytes(raw: &[u8]) -> CodecResult<Self> {
if raw.len() < UUID_LEN {
return Err(CodecError::InvalidBitstream(format!(
"UserDataUnregistered: need {UUID_LEN} bytes for UUID, got {}",
raw.len()
)));
}
let mut uuid = [0u8; UUID_LEN];
uuid.copy_from_slice(&raw[..UUID_LEN]);
Ok(Self {
uuid,
data: raw[UUID_LEN..].to_vec(),
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PictureTiming {
pub clock_timestamp_flag: bool,
pub clock_timestamp: u64,
pub presentation_delay: u32,
pub pic_struct: PicStructure,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum PicStructure {
Frame = 0,
TopField = 1,
BottomField = 2,
TopBottomField = 3,
BottomTopField = 4,
}
impl PicStructure {
fn from_byte(b: u8) -> Self {
match b {
1 => Self::TopField,
2 => Self::BottomField,
3 => Self::TopBottomField,
4 => Self::BottomTopField,
_ => Self::Frame,
}
}
}
impl PictureTiming {
pub fn frame(clock_timestamp: u64, presentation_delay: u32) -> Self {
Self {
clock_timestamp_flag: true,
clock_timestamp,
presentation_delay,
pic_struct: PicStructure::Frame,
}
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(16);
out.push(u8::from(self.clock_timestamp_flag));
out.extend_from_slice(&self.clock_timestamp.to_be_bytes());
out.extend_from_slice(&self.presentation_delay.to_be_bytes());
out.push(self.pic_struct as u8);
out.extend_from_slice(&[0u8; 2]); out
}
pub fn from_bytes(raw: &[u8]) -> CodecResult<Self> {
if raw.len() < 14 {
return Err(CodecError::InvalidBitstream(format!(
"PictureTiming: need 14 bytes, got {}",
raw.len()
)));
}
let clock_timestamp_flag = raw[0] != 0;
let clock_timestamp =
u64::from_be_bytes(raw[1..9].try_into().map_err(|_| {
CodecError::InvalidBitstream("PictureTiming: bad clock bytes".into())
})?);
let presentation_delay =
u32::from_be_bytes(raw[9..13].try_into().map_err(|_| {
CodecError::InvalidBitstream("PictureTiming: bad delay bytes".into())
})?);
let pic_struct = PicStructure::from_byte(raw[13]);
Ok(Self {
clock_timestamp_flag,
clock_timestamp,
presentation_delay,
pic_struct,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SeiMessage {
pub payload_type: SeiPayloadType,
pub payload: Vec<u8>,
}
impl SeiMessage {
pub fn new(payload_type: SeiPayloadType, payload: Vec<u8>) -> Self {
Self {
payload_type,
payload,
}
}
pub fn user_data_unregistered(udu: &UserDataUnregistered) -> Self {
Self::new(SeiPayloadType::UserDataUnregistered, udu.to_bytes())
}
pub fn picture_timing(pt: &PictureTiming) -> Self {
Self::new(SeiPayloadType::PictureTiming, pt.to_bytes())
}
pub fn as_user_data_unregistered(&self) -> CodecResult<UserDataUnregistered> {
if self.payload_type != SeiPayloadType::UserDataUnregistered {
return Err(CodecError::InvalidData(
"SEI: expected UserDataUnregistered payload type".into(),
));
}
UserDataUnregistered::from_bytes(&self.payload)
}
pub fn as_picture_timing(&self) -> CodecResult<PictureTiming> {
if self.payload_type != SeiPayloadType::PictureTiming {
return Err(CodecError::InvalidData(
"SEI: expected PictureTiming payload type".into(),
));
}
PictureTiming::from_bytes(&self.payload)
}
}
#[derive(Debug, Default)]
pub struct SeiEncoder {
buf: Vec<u8>,
}
impl SeiEncoder {
pub fn new() -> Self {
Self::default()
}
pub fn write_message(&mut self, msg: &SeiMessage) {
self.buf.push(msg.payload_type as u8);
let len = msg.payload.len() as u32;
self.buf.extend_from_slice(&len.to_be_bytes());
self.buf.extend_from_slice(&msg.payload);
}
pub fn write_messages(&mut self, msgs: &[SeiMessage]) {
for msg in msgs {
self.write_message(msg);
}
}
pub fn finish(self) -> Vec<u8> {
self.buf
}
pub fn len(&self) -> usize {
self.buf.len()
}
pub fn is_empty(&self) -> bool {
self.buf.is_empty()
}
}
#[derive(Debug)]
pub struct SeiDecoder<'a> {
data: &'a [u8],
pos: usize,
}
impl<'a> SeiDecoder<'a> {
pub fn new(data: &'a [u8]) -> Self {
Self { data, pos: 0 }
}
pub fn next_message(&mut self) -> CodecResult<Option<SeiMessage>> {
if self.pos >= self.data.len() {
return Ok(None);
}
let type_byte = self.data[self.pos];
self.pos += 1;
if self.pos + 4 > self.data.len() {
return Err(CodecError::InvalidBitstream(
"SEI: truncated length field".into(),
));
}
let length = u32::from_be_bytes(
self.data[self.pos..self.pos + 4]
.try_into()
.map_err(|_| CodecError::InvalidBitstream("SEI: bad length bytes".into()))?,
) as usize;
self.pos += 4;
if self.pos + length > self.data.len() {
return Err(CodecError::InvalidBitstream(format!(
"SEI: payload truncated (need {length}, have {})",
self.data.len() - self.pos
)));
}
let payload = self.data[self.pos..self.pos + length].to_vec();
self.pos += length;
Ok(Some(SeiMessage {
payload_type: SeiPayloadType::from_byte(type_byte),
payload,
}))
}
pub fn collect_all(&mut self) -> CodecResult<Vec<SeiMessage>> {
let mut result = Vec::new();
while let Some(msg) = self.next_message()? {
result.push(msg);
}
Ok(result)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Av1MetadataObu {
pub metadata_type: u8,
pub extension_flag: bool,
pub payload: Vec<u8>,
}
impl Av1MetadataObu {
pub fn new(metadata_type: u8, payload: Vec<u8>) -> Self {
Self {
metadata_type,
extension_flag: false,
payload,
}
}
pub fn to_bytes(&self) -> Vec<u8> {
let extension_bit = u8::from(self.extension_flag) << 2;
let obu_header = (AV1_OBU_TYPE_METADATA << 3) | extension_bit;
let mut out = Vec::with_capacity(2 + self.payload.len());
out.push(obu_header);
if self.extension_flag {
out.push(0x00); }
out.push(self.metadata_type);
out.extend_from_slice(&self.payload);
out
}
pub fn from_bytes(raw: &[u8]) -> CodecResult<Self> {
if raw.is_empty() {
return Err(CodecError::InvalidBitstream(
"Av1MetadataObu: empty input".into(),
));
}
let obu_header = raw[0];
let obu_type = (obu_header >> 3) & 0x0F;
if obu_type != AV1_OBU_TYPE_METADATA {
return Err(CodecError::InvalidBitstream(format!(
"Av1MetadataObu: expected type {AV1_OBU_TYPE_METADATA}, got {obu_type}"
)));
}
let extension_flag = (obu_header >> 2) & 1 != 0;
let mut pos = 1usize;
if extension_flag {
pos += 1; }
if pos >= raw.len() {
return Err(CodecError::InvalidBitstream(
"Av1MetadataObu: missing metadata_type".into(),
));
}
let metadata_type = raw[pos];
pos += 1;
let payload = raw[pos..].to_vec();
Ok(Self {
metadata_type,
extension_flag,
payload,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Vp8MetadataBlock {
pub payload: Vec<u8>,
}
impl Vp8MetadataBlock {
pub fn new(payload: Vec<u8>) -> Self {
Self { payload }
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(5 + self.payload.len());
out.push(VP8_USER_DATA_MARKER);
let len = self.payload.len() as u32;
out.extend_from_slice(&len.to_be_bytes());
out.extend_from_slice(&self.payload);
out
}
pub fn from_bytes(raw: &[u8]) -> CodecResult<Self> {
if raw.is_empty() || raw[0] != VP8_USER_DATA_MARKER {
return Err(CodecError::InvalidBitstream(
"Vp8MetadataBlock: missing marker byte".into(),
));
}
if raw.len() < 5 {
return Err(CodecError::InvalidBitstream(
"Vp8MetadataBlock: truncated header".into(),
));
}
let length = u32::from_be_bytes(
raw[1..5]
.try_into()
.map_err(|_| CodecError::InvalidBitstream("Vp8MetadataBlock: bad length".into()))?,
) as usize;
if raw.len() < 5 + length {
return Err(CodecError::InvalidBitstream(format!(
"Vp8MetadataBlock: payload truncated (need {length}, have {})",
raw.len() - 5
)));
}
Ok(Self {
payload: raw[5..5 + length].to_vec(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_user_data_unregistered_roundtrip() {
let uuid = [0x01u8; UUID_LEN];
let data = b"hello sei world".to_vec();
let udu = UserDataUnregistered::new(uuid, data.clone());
let raw = udu.to_bytes();
let parsed = UserDataUnregistered::from_bytes(&raw).unwrap();
assert_eq!(parsed.uuid, uuid);
assert_eq!(parsed.data, data);
}
#[test]
fn test_user_data_unregistered_too_short() {
let result = UserDataUnregistered::from_bytes(&[0u8; 10]);
assert!(result.is_err());
}
#[test]
fn test_picture_timing_roundtrip() {
let pt = PictureTiming::frame(123_456_789, 3000);
let raw = pt.to_bytes();
let parsed = PictureTiming::from_bytes(&raw).unwrap();
assert_eq!(parsed.clock_timestamp, 123_456_789);
assert_eq!(parsed.presentation_delay, 3000);
assert_eq!(parsed.pic_struct, PicStructure::Frame);
assert!(parsed.clock_timestamp_flag);
}
#[test]
fn test_picture_timing_too_short() {
let result = PictureTiming::from_bytes(&[0u8; 5]);
assert!(result.is_err());
}
#[test]
fn test_sei_encoder_decoder_roundtrip() {
let udu = UserDataUnregistered::with_nil_uuid(b"test payload".to_vec());
let pt = PictureTiming::frame(999, 500);
let mut enc = SeiEncoder::new();
enc.write_message(&SeiMessage::user_data_unregistered(&udu));
enc.write_message(&SeiMessage::picture_timing(&pt));
let bytes = enc.finish();
let mut dec = SeiDecoder::new(&bytes);
let messages = dec.collect_all().unwrap();
assert_eq!(messages.len(), 2);
assert_eq!(
messages[0].payload_type,
SeiPayloadType::UserDataUnregistered
);
let recovered_udu = messages[0].as_user_data_unregistered().unwrap();
assert_eq!(recovered_udu.data, b"test payload");
assert_eq!(messages[1].payload_type, SeiPayloadType::PictureTiming);
let recovered_pt = messages[1].as_picture_timing().unwrap();
assert_eq!(recovered_pt.clock_timestamp, 999);
}
#[test]
fn test_sei_decoder_truncated_length() {
let bad = &[SeiPayloadType::PictureTiming as u8];
let mut dec = SeiDecoder::new(bad);
assert!(dec.next_message().is_err());
}
#[test]
fn test_sei_decoder_truncated_payload() {
let mut enc = SeiEncoder::new();
enc.write_message(&SeiMessage::new(
SeiPayloadType::UserDataUnregistered,
vec![0u8; 20],
));
let mut bytes = enc.finish();
bytes.truncate(bytes.len() - 5);
let mut dec = SeiDecoder::new(&bytes);
assert!(dec.next_message().is_err());
}
#[test]
fn test_av1_metadata_obu_roundtrip() {
let sei_payload = b"av1 sei data".to_vec();
let obu = Av1MetadataObu::new(5, sei_payload.clone());
let bytes = obu.to_bytes();
let parsed = Av1MetadataObu::from_bytes(&bytes).unwrap();
assert_eq!(parsed.metadata_type, 5);
assert_eq!(parsed.payload, sei_payload);
assert!(!parsed.extension_flag);
}
#[test]
fn test_av1_metadata_obu_wrong_type() {
let bad = &[0x08u8, 0x00, 0x00]; let result = Av1MetadataObu::from_bytes(bad);
assert!(result.is_err());
}
#[test]
fn test_vp8_metadata_block_roundtrip() {
let payload = b"vp8 metadata payload".to_vec();
let block = Vp8MetadataBlock::new(payload.clone());
let bytes = block.to_bytes();
assert_eq!(bytes[0], VP8_USER_DATA_MARKER);
let parsed = Vp8MetadataBlock::from_bytes(&bytes).unwrap();
assert_eq!(parsed.payload, payload);
}
#[test]
fn test_vp8_metadata_block_bad_marker() {
let result = Vp8MetadataBlock::from_bytes(&[0x00, 0x00, 0x00, 0x00, 0x00]);
assert!(result.is_err());
}
#[test]
fn test_sei_payload_type_roundtrip() {
for &(byte, expected) in &[
(0u8, SeiPayloadType::BufferingPeriod),
(1, SeiPayloadType::PictureTiming),
(5, SeiPayloadType::UserDataUnregistered),
(255, SeiPayloadType::Unknown),
] {
assert_eq!(SeiPayloadType::from_byte(byte), expected);
}
}
}