use crate::PACKET_START_CODE_PREFIX;
use crate::error::{Error, Result};
use crate::stream_id::StreamId;
use crate::timestamp::{self, Dts, Pts};
const MIN_LEN: usize = 6; const HEADER_FIXED: usize = 3;
const F1_PRIORITY: u8 = 0x08;
const F1_DATA_ALIGNMENT: u8 = 0x04;
const F1_COPYRIGHT: u8 = 0x02;
const F1_ORIGINAL_OR_COPY: u8 = 0x01;
const F2_ESCR: u8 = 0x20;
const F2_ES_RATE: u8 = 0x10;
const F2_DSM_TRICK_MODE: u8 = 0x08;
const F2_ADDITIONAL_COPY_INFO: u8 = 0x04;
const F2_PES_CRC: u8 = 0x02;
const F2_PES_EXTENSION: u8 = 0x01;
const EXT_PES_PRIVATE_DATA: u8 = 0x80;
const EXT_PACK_HEADER: u8 = 0x40;
const EXT_PROGRAM_PACKET_SEQUENCE_COUNTER: u8 = 0x20;
const EXT_P_STD_BUFFER: u8 = 0x10;
const EXT_FLAG_2: u8 = 0x01;
const EXT_RESERVED_MASK: u8 = 0x0E;
const EXT_RESERVED_SHIFT: u32 = 1;
const EXT_RESERVED_VALUE: u8 = 0x07;
const MSB_FLAG: u8 = 0x80;
const F1_MARKER_MASK: u8 = 0xC0;
const F1_MARKER_VALUE: u8 = 0x80;
const ES_RATE_MARKERS_VALUE: u8 = 0b11;
const ES_RATE_LEADING_MARKER: u8 = 0x80;
const ES_RATE_TRAILING_MARKER: u8 = 0x01;
const LOW7_MASK: u8 = 0x7F;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Escr {
pub base: u64,
pub extension: u16,
pub reserved: u8,
}
pub const FREEZE_FRAME_RESERVED_VALUE: u8 = 0b111;
const ADDITIONAL_COPY_INFO_MARKER: u8 = 0x80;
const ESCR_RESERVED_VALUE: u8 = 0b11;
const ESCR_RESERVED_SHIFT: u32 = 6;
const ESCR_BASE_MASK: u64 = (1 << 33) - 1;
const ESCR_EXT_MASK: u16 = (1 << 9) - 1;
const ESCR_MARKER_MID: u8 = 0x04;
const ESCR_MARKER_LAST: u8 = 0x01;
impl Escr {
#[must_use]
pub fn as_27mhz(self) -> u64 {
broadcast_common::clock33::pcr_from_parts(self.base, self.extension)
}
#[must_use]
pub fn from_27mhz(ticks: u64) -> Self {
let (base, extension) = broadcast_common::clock33::pcr_split(ticks);
Self {
base,
extension,
reserved: ESCR_RESERVED_VALUE,
}
}
pub fn from_field_bytes(b: &[u8; 6]) -> Result<Self> {
let base = ((((b[0] >> 3) & 0x07) as u64) << 30) | (((b[0] & 0x03) as u64) << 28) | ((b[1] as u64) << 20) | ((((b[2] >> 3) & 0x1F) as u64) << 15) | (((b[2] & 0x03) as u64) << 13) | ((b[3] as u64) << 5) | (((b[4] >> 3) & 0x1F) as u64); let extension = ((((b[4] & 0x03) as u16) << 7) | ((b[5] >> 1) as u16)) & ESCR_EXT_MASK;
Ok(Self {
base,
extension,
reserved: b[0] >> ESCR_RESERVED_SHIFT,
})
}
#[must_use]
pub fn to_field_bytes(self) -> [u8; 6] {
let b = self.base & ESCR_BASE_MASK;
let e = (self.extension & ESCR_EXT_MASK) as u64;
[
((self.reserved & ESCR_RESERVED_VALUE) << ESCR_RESERVED_SHIFT)
| (((b >> 30) & 0x07) as u8) << 3
| ESCR_MARKER_MID
| ((b >> 28) & 0x03) as u8,
((b >> 20) & 0xFF) as u8,
(((b >> 15) & 0x1F) as u8) << 3 | ESCR_MARKER_MID | ((b >> 13) & 0x03) as u8,
((b >> 5) & 0xFF) as u8,
(((b & 0x1F) as u8) << 3) | ESCR_MARKER_MID | ((e >> 7) & 0x03) as u8,
(((e & 0x7F) as u8) << 1) | ESCR_MARKER_LAST,
]
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum TrickMode {
FastForward {
field_id: u8,
intra_slice_refresh: bool,
frequency_truncation: u8,
},
SlowMotion {
rep_cntrl: u8,
},
FreezeFrame {
field_id: u8,
reserved: u8,
},
FastReverse {
field_id: u8,
intra_slice_refresh: bool,
frequency_truncation: u8,
},
SlowReverse {
rep_cntrl: u8,
},
Reserved {
trick_mode_control: u8,
data: u8,
},
}
impl TrickMode {
pub fn from_byte(b: u8) -> Self {
let control = (b >> 5) & 0x07;
let data = b & 0x1F;
match control {
0b000 => TrickMode::FastForward {
field_id: (data >> 3) & 0x03,
intra_slice_refresh: (data >> 2) & 0x01 != 0,
frequency_truncation: data & 0x03,
},
0b001 => TrickMode::SlowMotion { rep_cntrl: data },
0b010 => TrickMode::FreezeFrame {
field_id: (data >> 3) & 0x03,
reserved: data & 0x07,
},
0b011 => TrickMode::FastReverse {
field_id: (data >> 3) & 0x03,
intra_slice_refresh: (data >> 2) & 0x01 != 0,
frequency_truncation: data & 0x03,
},
0b100 => TrickMode::SlowReverse { rep_cntrl: data },
_ => TrickMode::Reserved {
trick_mode_control: control,
data,
},
}
}
pub fn to_byte(self) -> u8 {
match self {
TrickMode::FastForward {
field_id,
intra_slice_refresh,
frequency_truncation,
} => {
((field_id & 0x03) << 3)
| ((intra_slice_refresh as u8) << 2)
| (frequency_truncation & 0x03)
}
TrickMode::SlowMotion { rep_cntrl } => (0b001 << 5) | (rep_cntrl & 0x1F),
TrickMode::FreezeFrame { field_id, reserved } => {
(0b010 << 5) | ((field_id & 0x03) << 3) | (reserved & 0x07)
}
TrickMode::FastReverse {
field_id,
intra_slice_refresh,
frequency_truncation,
} => {
(0b011 << 5)
| ((field_id & 0x03) << 3)
| ((intra_slice_refresh as u8) << 2)
| (frequency_truncation & 0x03)
}
TrickMode::SlowReverse { rep_cntrl } => (0b100 << 5) | (rep_cntrl & 0x1F),
TrickMode::Reserved {
trick_mode_control,
data,
} => ((trick_mode_control & 0x07) << 5) | (data & 0x1F),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ProgramPacketSequenceCounter {
pub counter: u8,
pub mpeg1_mpeg2_identifier: bool,
pub original_stuff_length: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct PStdBuffer {
pub scale: bool,
pub size: u16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct PesExtensionField<'a> {
pub stream_id_extension_flag: bool,
pub low_bits: u8,
pub rest: &'a [u8],
}
impl PesExtensionField<'_> {
#[must_use]
pub const fn stream_id_extension(&self) -> Option<u8> {
if self.stream_id_extension_flag {
None
} else {
Some(self.low_bits)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct AdditionalCopyInfo {
pub info: u8,
pub marker: bool,
}
impl AdditionalCopyInfo {
#[must_use]
pub const fn new(info: u8) -> Self {
Self { info, marker: true }
}
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct PesExtension<'a> {
pub pes_private_data: Option<[u8; 16]>,
pub pack_header: Option<&'a [u8]>,
pub program_packet_sequence_counter: Option<ProgramPacketSequenceCounter>,
pub p_std_buffer: Option<PStdBuffer>,
pub pes_extension_field: Option<PesExtensionField<'a>>,
pub reserved_bits: u8,
}
impl Default for PesExtension<'_> {
fn default() -> Self {
Self::new()
}
}
impl<'a> PesExtension<'a> {
#[must_use]
pub const fn new() -> Self {
Self {
pes_private_data: None,
pack_header: None,
program_packet_sequence_counter: None,
p_std_buffer: None,
pes_extension_field: None,
reserved_bits: EXT_RESERVED_VALUE,
}
}
fn parse(data: &'a [u8]) -> Result<Self> {
if data.is_empty() {
return Err(Error::BufferTooShort {
need: 1,
have: 0,
what: "PES_extension flags byte",
});
}
let flags = data[0];
let mut cursor = 1usize;
let pes_private_data = if flags & EXT_PES_PRIVATE_DATA != 0 {
let end = cursor + 16;
let arr: [u8; 16] = data
.get(cursor..end)
.and_then(|s| s.try_into().ok())
.ok_or(Error::BufferTooShort {
need: end,
have: data.len(),
what: "PES_private_data",
})?;
cursor = end;
Some(arr)
} else {
None
};
let pack_header = if flags & EXT_PACK_HEADER != 0 {
let pack_len = *data.get(cursor).ok_or(Error::BufferTooShort {
need: cursor + 1,
have: data.len(),
what: "pack_field_length",
})? as usize;
cursor += 1;
let end = cursor + pack_len;
let slice = data.get(cursor..end).ok_or(Error::BufferTooShort {
need: end,
have: data.len(),
what: "pack_header",
})?;
cursor = end;
Some(slice)
} else {
None
};
let program_packet_sequence_counter = if flags & EXT_PROGRAM_PACKET_SEQUENCE_COUNTER != 0 {
if data.len() < cursor + 2 {
return Err(Error::BufferTooShort {
need: cursor + 2,
have: data.len(),
what: "program_packet_sequence_counter",
});
}
let b0 = data[cursor];
let b1 = data[cursor + 1];
cursor += 2;
Some(ProgramPacketSequenceCounter {
counter: b0 & 0x7F,
mpeg1_mpeg2_identifier: (b1 & 0x40) != 0,
original_stuff_length: b1 & 0x3F,
})
} else {
None
};
let p_std_buffer = if flags & EXT_P_STD_BUFFER != 0 {
if data.len() < cursor + 2 {
return Err(Error::BufferTooShort {
need: cursor + 2,
have: data.len(),
what: "P-STD_buffer",
});
}
let b0 = data[cursor];
let b1 = data[cursor + 1];
cursor += 2;
Some(PStdBuffer {
scale: (b0 & 0x20) != 0,
size: (((b0 & 0x1F) as u16) << 8) | (b1 as u16),
})
} else {
None
};
let pes_extension_field = if flags & EXT_FLAG_2 != 0 {
let len_byte = *data.get(cursor).ok_or(Error::BufferTooShort {
need: cursor + 1,
have: data.len(),
what: "PES_extension_field_length",
})?;
let ext_len = (len_byte & LOW7_MASK) as usize;
cursor += 1;
let end = cursor + ext_len;
let field_bytes = data.get(cursor..end).ok_or(Error::BufferTooShort {
need: end,
have: data.len(),
what: "PES_extension_field",
})?;
cursor = end;
let (sie_byte, rest) = field_bytes.split_first().ok_or(Error::BufferTooShort {
need: 1,
have: 0,
what: "PES_extension_field.stream_id_extension byte",
})?;
Some(PesExtensionField {
stream_id_extension_flag: (sie_byte & MSB_FLAG) != 0,
low_bits: sie_byte & LOW7_MASK,
rest,
})
} else {
None
};
let _ = cursor;
Ok(PesExtension {
pes_private_data,
pack_header,
program_packet_sequence_counter,
p_std_buffer,
pes_extension_field,
reserved_bits: (flags & EXT_RESERVED_MASK) >> EXT_RESERVED_SHIFT,
})
}
pub fn serialized_len(&self) -> usize {
let mut n = 1usize; if self.pes_private_data.is_some() {
n += 16;
}
if let Some(ph) = self.pack_header {
n += 1 + ph.len();
}
if self.program_packet_sequence_counter.is_some() {
n += 2;
}
if self.p_std_buffer.is_some() {
n += 2;
}
if let Some(ef) = &self.pes_extension_field {
n += 1 + 1 + ef.rest.len();
}
n
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let need = self.serialized_len();
if buf.len() < need {
return Err(Error::BufferTooShort {
need,
have: buf.len(),
what: "PES_extension serialize output",
});
}
let mut flags = broadcast_common::len::fit_mask(
self.reserved_bits,
EXT_RESERVED_VALUE,
"PES_extension.reserved",
)? << EXT_RESERVED_SHIFT;
if self.pes_private_data.is_some() {
flags |= EXT_PES_PRIVATE_DATA;
}
if self.pack_header.is_some() {
flags |= EXT_PACK_HEADER;
}
if self.program_packet_sequence_counter.is_some() {
flags |= EXT_PROGRAM_PACKET_SEQUENCE_COUNTER;
}
if self.p_std_buffer.is_some() {
flags |= EXT_P_STD_BUFFER;
}
if self.pes_extension_field.is_some() {
flags |= EXT_FLAG_2;
}
buf[0] = flags;
let mut cursor = 1usize;
if let Some(pd) = &self.pes_private_data {
buf[cursor..cursor + 16].copy_from_slice(pd);
cursor += 16;
}
if let Some(ph) = self.pack_header {
buf[cursor] =
broadcast_common::len::fit_u8(ph.len(), "PES_extension.pack_field_length")?;
cursor += 1;
buf[cursor..cursor + ph.len()].copy_from_slice(ph);
cursor += ph.len();
}
if let Some(ppsc) = self.program_packet_sequence_counter {
buf[cursor] = 0x80 | (ppsc.counter & 0x7F);
buf[cursor + 1] = 0x80
| ((ppsc.mpeg1_mpeg2_identifier as u8) << 6)
| (ppsc.original_stuff_length & 0x3F);
cursor += 2;
}
if let Some(ps) = self.p_std_buffer {
let size = broadcast_common::len::fit_bits(
u64::from(ps.size),
13,
"PES_extension.P-STD_buffer_size",
)? as u16;
buf[cursor] = 0x40 | ((ps.scale as u8) << 5) | ((size >> 8) as u8 & 0x1F);
buf[cursor + 1] = (size & 0xFF) as u8;
cursor += 2;
}
if let Some(ef) = &self.pes_extension_field {
let field_len = 1 + ef.rest.len(); let checked = broadcast_common::len::fit_bits(
field_len as u64,
7,
"PES_extension.PES_extension_field_length",
)?;
buf[cursor] = MSB_FLAG | (checked as u8);
cursor += 1;
buf[cursor] = ((ef.stream_id_extension_flag as u8) << 7) | (ef.low_bits & LOW7_MASK);
cursor += 1;
buf[cursor..cursor + ef.rest.len()].copy_from_slice(ef.rest);
cursor += ef.rest.len();
}
Ok(cursor)
}
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct PesHeader<'a> {
pub scrambling_control: u8,
pub pes_priority: bool,
pub data_alignment_indicator: bool,
pub copyright: bool,
pub original_or_copy: bool,
pub pts: Option<Pts>,
pub dts: Option<Dts>,
pub escr: Option<Escr>,
pub es_rate: Option<u32>,
pub es_rate_marker_bits: u8,
pub dsm_trick_mode: Option<TrickMode>,
pub additional_copy_info: Option<AdditionalCopyInfo>,
pub pes_crc: Option<u16>,
pub pes_extension: Option<PesExtension<'a>>,
pub header_stuffing: &'a [u8],
}
impl<'a> PesHeader<'a> {
#[must_use]
pub const fn new() -> Self {
Self {
scrambling_control: 0,
pes_priority: false,
data_alignment_indicator: false,
copyright: false,
original_or_copy: false,
pts: None,
dts: None,
escr: None,
es_rate: None,
es_rate_marker_bits: ES_RATE_MARKERS_VALUE,
dsm_trick_mode: None,
additional_copy_info: None,
pes_crc: None,
pes_extension: None,
header_stuffing: &[],
}
}
}
impl Default for PesHeader<'_> {
fn default() -> Self {
Self::new()
}
}
impl PesHeader<'_> {
fn optional_len(&self) -> usize {
let mut n = 0usize;
if self.pts.is_some() {
n += 5;
}
if self.dts.is_some() {
n += 5;
}
if self.escr.is_some() {
n += 6;
}
if self.es_rate.is_some() {
n += 3;
}
if self.dsm_trick_mode.is_some() {
n += 1;
}
if self.additional_copy_info.is_some() {
n += 1;
}
if self.pes_crc.is_some() {
n += 2;
}
if let Some(ref ext) = self.pes_extension {
n += ext.serialized_len();
}
n += self.header_stuffing.len();
n
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct PesPacket<'a> {
pub stream_id: StreamId,
pub pes_packet_length: u16,
pub header: Option<PesHeader<'a>>,
#[cfg_attr(feature = "serde", serde(skip))]
pub payload: &'a [u8],
}
impl<'a> PesPacket<'a> {
pub fn parse(b: &'a [u8]) -> Result<Self> {
if b.len() < MIN_LEN {
return Err(Error::BufferTooShort {
need: MIN_LEN,
have: b.len(),
what: "PES packet header",
});
}
if b[0..3] != PACKET_START_CODE_PREFIX {
return Err(Error::BadStartCode(
(u32::from(b[0]) << 16) | (u32::from(b[1]) << 8) | u32::from(b[2]),
));
}
let stream_id = StreamId(b[3]);
let pes_packet_length = u16::from_be_bytes([b[4], b[5]]);
let payload_end = if pes_packet_length == 0 {
b.len()
} else {
(MIN_LEN + pes_packet_length as usize).min(b.len())
};
if !stream_id.has_optional_header() {
return Ok(PesPacket {
stream_id,
pes_packet_length,
header: None,
payload: &b[MIN_LEN..payload_end],
});
}
if b.len() < MIN_LEN + HEADER_FIXED {
return Err(Error::BufferTooShort {
need: MIN_LEN + HEADER_FIXED,
have: b.len(),
what: "PES optional header",
});
}
let f1 = b[6];
let f2 = b[7];
if f1 & F1_MARKER_MASK != F1_MARKER_VALUE {
return Err(Error::BadHeaderMarker);
}
let hdl = usize::from(b[8]);
let hdr_start = MIN_LEN + HEADER_FIXED; let hdr_end = hdr_start + hdl;
if b.len() < hdr_end {
return Err(Error::BufferTooShort {
need: hdr_end,
have: b.len(),
what: "PES_header_data_length",
});
}
if pes_packet_length != 0 && hdr_end > payload_end {
return Err(Error::BufferTooShort {
need: hdr_end,
have: payload_end,
what: "PES_header_data_length exceeds PES_packet_length",
});
}
let opt = &b[hdr_start..hdr_end];
let mut cursor = 0usize;
let pts_dts_flags = (f2 >> 6) & 0x03;
let (pts, dts) = match pts_dts_flags {
0b10 => {
if opt.len() < cursor + 5 {
return Err(Error::BufferTooShort {
need: cursor + 5,
have: opt.len(),
what: "PTS",
});
}
let pts = Pts(timestamp::read(&opt[cursor..], 0b0010, "PTS")?);
cursor += 5;
(Some(pts), None)
}
0b11 => {
if opt.len() < cursor + 10 {
return Err(Error::BufferTooShort {
need: cursor + 10,
have: opt.len(),
what: "PTS+DTS",
});
}
let pts = Pts(timestamp::read(&opt[cursor..], 0b0011, "PTS")?);
cursor += 5;
let dts = Dts(timestamp::read(&opt[cursor..], 0b0001, "DTS")?);
cursor += 5;
(Some(pts), Some(dts))
}
0b00 => (None, None),
_ => return Err(Error::ForbiddenPtsDtsFlags),
};
let escr = if f2 & F2_ESCR != 0 {
if opt.len() < cursor + 6 {
return Err(Error::BufferTooShort {
need: cursor + 6,
have: opt.len(),
what: "ESCR",
});
}
let arr: &[u8; 6] = opt[cursor..cursor + 6].try_into().unwrap();
let e = Escr::from_field_bytes(arr)?;
cursor += 6;
Some(e)
} else {
None
};
let mut es_rate_marker_bits = ES_RATE_MARKERS_VALUE;
let es_rate = if f2 & F2_ES_RATE != 0 {
if opt.len() < cursor + 3 {
return Err(Error::BufferTooShort {
need: cursor + 3,
have: opt.len(),
what: "ES_rate",
});
}
es_rate_marker_bits = ((opt[cursor] >> 7) << 1) | (opt[cursor + 2] & 1);
let rate = (((opt[cursor] & 0x7F) as u32) << 15)
| ((opt[cursor + 1] as u32) << 7)
| ((opt[cursor + 2] >> 1) as u32);
cursor += 3;
Some(rate)
} else {
None
};
let dsm_trick_mode = if f2 & F2_DSM_TRICK_MODE != 0 {
if opt.len() < cursor + 1 {
return Err(Error::BufferTooShort {
need: cursor + 1,
have: opt.len(),
what: "trick_mode",
});
}
let tm = TrickMode::from_byte(opt[cursor]);
cursor += 1;
Some(tm)
} else {
None
};
let additional_copy_info = if f2 & F2_ADDITIONAL_COPY_INFO != 0 {
if opt.len() < cursor + 1 {
return Err(Error::BufferTooShort {
need: cursor + 1,
have: opt.len(),
what: "additional_copy_info",
});
}
let v = AdditionalCopyInfo {
info: opt[cursor] & 0x7F,
marker: opt[cursor] & ADDITIONAL_COPY_INFO_MARKER != 0,
};
cursor += 1;
Some(v)
} else {
None
};
let pes_crc = if f2 & F2_PES_CRC != 0 {
if opt.len() < cursor + 2 {
return Err(Error::BufferTooShort {
need: cursor + 2,
have: opt.len(),
what: "PES_CRC",
});
}
let crc = u16::from_be_bytes([opt[cursor], opt[cursor + 1]]);
cursor += 2;
Some(crc)
} else {
None
};
let pes_extension = if f2 & F2_PES_EXTENSION != 0 {
let ext = PesExtension::parse(&opt[cursor..])?;
cursor += ext.serialized_len();
Some(ext)
} else {
None
};
let header_stuffing = opt.get(cursor..).unwrap_or(&[]);
let header = PesHeader {
scrambling_control: (f1 >> 4) & 0x03,
pes_priority: f1 & F1_PRIORITY != 0,
data_alignment_indicator: f1 & F1_DATA_ALIGNMENT != 0,
copyright: f1 & F1_COPYRIGHT != 0,
original_or_copy: f1 & F1_ORIGINAL_OR_COPY != 0,
pts,
dts,
escr,
es_rate,
es_rate_marker_bits,
dsm_trick_mode,
additional_copy_info,
pes_crc,
pes_extension,
header_stuffing,
};
Ok(PesPacket {
stream_id,
pes_packet_length,
header: Some(header),
payload: &b[hdr_end.min(payload_end)..payload_end],
})
}
#[must_use]
pub fn serialized_len(&self) -> usize {
let hdr = self
.header
.as_ref()
.map_or(0, |h| HEADER_FIXED + h.optional_len());
MIN_LEN + hdr + self.payload.len()
}
pub fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let len = self.serialized_len();
if buf.len() < len {
return Err(Error::BufferTooShort {
need: len,
have: buf.len(),
what: "PES serialize output",
});
}
buf[0..3].copy_from_slice(&PACKET_START_CODE_PREFIX);
buf[3] = self.stream_id.0;
buf[4..6].copy_from_slice(&self.pes_packet_length.to_be_bytes());
let payload_at = match &self.header {
None => MIN_LEN,
Some(h) => {
if h.dts.is_some() && h.pts.is_none() {
return Err(Error::ForbiddenPtsDtsFlags);
}
let opt_len = h.optional_len();
if opt_len > 255 {
return Err(Error::OptionalFieldsTooLarge(opt_len));
}
let f1 = 0x80
| ((h.scrambling_control & 0x03) << 4)
| (u8::from(h.pes_priority) << 3)
| (u8::from(h.data_alignment_indicator) << 2)
| (u8::from(h.copyright) << 1)
| u8::from(h.original_or_copy);
let pts_dts_flags = match (h.pts.is_some(), h.dts.is_some()) {
(true, true) => 0b11u8,
(true, false) => 0b10,
_ => 0b00,
};
let f2 = (pts_dts_flags << 6)
| (u8::from(h.escr.is_some()) << 5)
| (u8::from(h.es_rate.is_some()) << 4)
| (u8::from(h.dsm_trick_mode.is_some()) << 3)
| (u8::from(h.additional_copy_info.is_some()) << 2)
| (u8::from(h.pes_crc.is_some()) << 1)
| u8::from(h.pes_extension.is_some());
buf[6] = f1;
buf[7] = f2;
buf[8] = opt_len as u8;
let mut cursor = MIN_LEN + HEADER_FIXED;
if let Some(pts) = h.pts {
let prefix = if h.dts.is_some() { 0b0011u8 } else { 0b0010u8 };
buf[cursor..cursor + 5].copy_from_slice(×tamp::write(pts.0, prefix));
cursor += 5;
}
if let Some(dts) = h.dts {
buf[cursor..cursor + 5].copy_from_slice(×tamp::write(dts.0, 0b0001));
cursor += 5;
}
if let Some(escr) = h.escr {
buf[cursor..cursor + 6].copy_from_slice(&escr.to_field_bytes());
cursor += 6;
}
if let Some(rate) = h.es_rate {
let leading = if h.es_rate_marker_bits & 0b10 != 0 {
ES_RATE_LEADING_MARKER
} else {
0
};
buf[cursor] = leading | ((rate >> 15) as u8 & 0x7F);
buf[cursor + 1] = ((rate >> 7) & 0xFF) as u8;
buf[cursor + 2] = (((rate & 0x7F) as u8) << 1)
| (h.es_rate_marker_bits & ES_RATE_TRAILING_MARKER);
cursor += 3;
}
if let Some(tm) = h.dsm_trick_mode {
buf[cursor] = tm.to_byte();
cursor += 1;
}
if let Some(aci) = h.additional_copy_info {
let marker = if aci.marker {
ADDITIONAL_COPY_INFO_MARKER
} else {
0
};
buf[cursor] = marker | (aci.info & 0x7F);
cursor += 1;
}
if let Some(crc) = h.pes_crc {
buf[cursor..cursor + 2].copy_from_slice(&crc.to_be_bytes());
cursor += 2;
}
if let Some(ref ext) = h.pes_extension {
let written = ext.serialize_into(&mut buf[cursor..])?;
cursor += written;
}
buf[cursor..cursor + h.header_stuffing.len()].copy_from_slice(h.header_stuffing);
cursor += h.header_stuffing.len();
cursor
}
};
buf[payload_at..len].copy_from_slice(self.payload);
Ok(len)
}
}
#[cfg(test)]
mod tests {
use super::*;
extern crate alloc;
use alloc::vec;
fn round_trip(b: &[u8]) {
let pkt = PesPacket::parse(b).unwrap();
let mut out = vec![0u8; pkt.serialized_len()];
pkt.serialize_into(&mut out).unwrap();
assert_eq!(&out[..], b, "round-trip mismatch");
let re = PesPacket::parse(&out).unwrap();
let mut re_out = vec![0u8; re.serialized_len()];
re.serialize_into(&mut re_out).unwrap();
assert_eq!(out, re_out, "re-parse mismatch");
}
#[test]
fn video_pts_only() {
let b = [
0x00, 0x00, 0x01, 0xE0, 0x00, 0x0A, 0x80, 0x80, 0x05, 0x21, 0x00, 0x01, 0x00, 0x01,
0xAA, 0xBB,
];
let pkt = PesPacket::parse(&b).unwrap();
assert_eq!(pkt.stream_id, StreamId(0xE0));
let h = pkt.header.as_ref().unwrap();
assert_eq!(h.pts, Some(Pts(0)));
assert!(h.dts.is_none());
assert_eq!(pkt.payload, &[0xAA, 0xBB]);
round_trip(&b);
}
#[test]
fn pts_and_dts() {
let b = [
0x00, 0x00, 0x01, 0xE0, 0x00, 0x0F, 0x80, 0xC0, 0x0A, 0x31, 0x00, 0x03, 0x00, 0x01,
0x11, 0x00, 0x05, 0x00, 0x01, 0xCC,
];
let pkt = PesPacket::parse(&b).unwrap();
let h = pkt.header.as_ref().unwrap();
assert!(h.pts.is_some());
assert!(h.dts.is_some());
round_trip(&b);
}
#[test]
fn forbidden_pts_dts_flags_01_is_rejected() {
let mut b = [
0x00, 0x00, 0x01, 0xE0, 0x00, 0x0F, 0x80, 0xC0, 0x0A, 0x31, 0x00, 0x03, 0x00, 0x01,
0x11, 0x00, 0x05, 0x00, 0x01, 0xCC,
];
b[7] = 0x40; assert!(matches!(
PesPacket::parse(&b),
Err(Error::ForbiddenPtsDtsFlags)
));
}
#[test]
fn serialize_rejects_dts_without_pts() {
let header = PesHeader {
scrambling_control: 0,
pes_priority: false,
data_alignment_indicator: false,
copyright: false,
original_or_copy: false,
pts: None,
dts: Some(Dts(0)),
escr: None,
es_rate: None,
es_rate_marker_bits: 0b11,
dsm_trick_mode: None,
additional_copy_info: None,
pes_crc: None,
pes_extension: None,
header_stuffing: &[],
};
let pkt = PesPacket {
stream_id: StreamId(0xE0),
pes_packet_length: 0,
header: Some(header),
payload: &[0xAA],
};
let mut buf = vec![0u8; pkt.serialized_len()];
assert!(matches!(
pkt.serialize_into(&mut buf),
Err(Error::ForbiddenPtsDtsFlags)
));
}
#[test]
fn pes_header_stuffing_round_trip() {
let b = [
0x00, 0x00, 0x01, 0xE0, 0x00, 0x0C, 0x80, 0x80, 0x08, 0x21, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xAA, ];
let pkt = PesPacket::parse(&b).unwrap();
let h = pkt.header.as_ref().unwrap();
assert!(h.pts.is_some());
assert_eq!(
h.header_stuffing,
&[0xFF, 0xFF, 0xFF],
"3 stuffing bytes after the 5-byte PTS"
);
assert_eq!(pkt.serialized_len(), b.len());
round_trip(&b); }
#[test]
fn special_stream_no_header() {
let b = [0x00, 0x00, 0x01, 0xBE, 0x00, 0x03, 0xFF, 0xFF, 0xFF];
let pkt = PesPacket::parse(&b).unwrap();
assert!(pkt.header.is_none());
assert_eq!(pkt.payload, &[0xFF, 0xFF, 0xFF]);
round_trip(&b);
}
#[test]
fn unbounded_length_zero() {
let b = [
0x00, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x80, 0x80, 0x05, 0x21, 0x00, 0x01, 0x00, 0x01,
0x01, 0x02, 0x03,
];
let pkt = PesPacket::parse(&b).unwrap();
assert_eq!(pkt.pes_packet_length, 0);
assert_eq!(pkt.payload, &[0x01, 0x02, 0x03]);
round_trip(&b);
}
#[test]
fn rejects_bad_start_code() {
let err = PesPacket::parse(&[0x00, 0x00, 0x02, 0xE0, 0x00, 0x00]).unwrap_err();
assert!(matches!(err, Error::BadStartCode(0x000002)));
}
#[test]
fn rejects_short() {
let err = PesPacket::parse(&[0x00, 0x00, 0x01]).unwrap_err();
assert!(matches!(err, Error::BufferTooShort { .. }));
}
#[test]
fn serialize_rejects_oversized_optional_fields() {
let _ = Error::OptionalFieldsTooLarge(256);
}
fn build_pes(h: PesHeader<'_>, payload: &[u8]) -> alloc::vec::Vec<u8> {
let pkt = PesPacket {
stream_id: StreamId(0xE0),
pes_packet_length: 0, header: Some(h),
payload,
};
let mut out = vec![0u8; pkt.serialized_len()];
pkt.serialize_into(&mut out).unwrap();
out
}
fn empty_header<'a>() -> PesHeader<'a> {
PesHeader {
scrambling_control: 0,
pes_priority: false,
data_alignment_indicator: false,
copyright: false,
original_or_copy: false,
pts: None,
dts: None,
escr: None,
es_rate: None,
es_rate_marker_bits: 0b11,
dsm_trick_mode: None,
additional_copy_info: None,
pes_crc: None,
pes_extension: None,
header_stuffing: &[],
}
}
#[test]
fn pes_header_escr_round_trip() {
let escr = Escr {
base: 90_000,
extension: 150,
reserved: 0b11,
};
let h = PesHeader {
escr: Some(escr),
..empty_header()
};
let bytes = build_pes(h, &[0xAA]);
let pkt = PesPacket::parse(&bytes).unwrap();
assert_eq!(pkt.header.unwrap().escr, Some(escr));
}
#[test]
fn escr_27mhz_round_trip() {
for ticks in [0u64, 1, 300, 27_000_000, 8_589_934_591] {
let e = Escr::from_27mhz(ticks);
assert_eq!(e.as_27mhz(), ticks, "ticks={ticks}");
}
}
#[test]
fn escr_field_bytes_round_trip() {
for (base, ext) in [
(0u64, 0u16),
(10_000, 0),
(0x1_FFFF_FFFF, 0x1FF),
(1234, 56),
] {
let e = Escr {
base,
extension: ext,
reserved: 0b11,
};
let bytes = e.to_field_bytes();
let decoded = Escr::from_field_bytes(&bytes).unwrap();
assert_eq!(decoded, e, "base={base} ext={ext}");
}
}
#[test]
fn pes_header_es_rate_round_trip() {
let h = PesHeader {
es_rate: Some(0x3FFFFF),
es_rate_marker_bits: 0b11,
..empty_header()
};
let bytes = build_pes(h, &[]);
let pkt = PesPacket::parse(&bytes).unwrap();
assert_eq!(pkt.header.unwrap().es_rate, Some(0x3FFFFF));
}
#[test]
fn trick_mode_all_variants_round_trip() {
let cases = [
TrickMode::FastForward {
field_id: 0x2,
intra_slice_refresh: true,
frequency_truncation: 0x3,
},
TrickMode::SlowMotion { rep_cntrl: 0x1F },
TrickMode::FreezeFrame {
field_id: 0x1,
reserved: 0b101,
},
TrickMode::FastReverse {
field_id: 0x0,
intra_slice_refresh: false,
frequency_truncation: 0x1,
},
TrickMode::SlowReverse { rep_cntrl: 0 },
TrickMode::Reserved {
trick_mode_control: 0b101,
data: 0x1A,
},
];
for tm in cases {
let b = tm.to_byte();
let decoded = TrickMode::from_byte(b);
assert_eq!(decoded, tm, "tm={tm:?}");
}
}
#[test]
fn pes_header_trick_mode_round_trip() {
let tm = TrickMode::FastForward {
field_id: 1,
intra_slice_refresh: false,
frequency_truncation: 2,
};
let h = PesHeader {
dsm_trick_mode: Some(tm),
..empty_header()
};
let bytes = build_pes(h, &[]);
let pkt = PesPacket::parse(&bytes).unwrap();
assert_eq!(pkt.header.unwrap().dsm_trick_mode, Some(tm));
}
#[test]
fn pes_header_additional_copy_info_round_trip() {
let h = PesHeader {
additional_copy_info: Some(AdditionalCopyInfo::new(0x7F)),
..empty_header()
};
let bytes = build_pes(h, &[]);
let pkt = PesPacket::parse(&bytes).unwrap();
assert_eq!(
pkt.header.unwrap().additional_copy_info,
Some(AdditionalCopyInfo::new(0x7F))
);
}
#[test]
fn pes_header_pes_crc_round_trip() {
let h = PesHeader {
pes_crc: Some(0xDEAD),
..empty_header()
};
let bytes = build_pes(h, &[]);
let pkt = PesPacket::parse(&bytes).unwrap();
assert_eq!(pkt.header.unwrap().pes_crc, Some(0xDEAD));
}
#[test]
fn pes_extension_ppsc_round_trip() {
let ppsc = ProgramPacketSequenceCounter {
counter: 42,
mpeg1_mpeg2_identifier: true,
original_stuff_length: 7,
};
let ext = PesExtension {
pes_private_data: None,
pack_header: None,
program_packet_sequence_counter: Some(ppsc),
p_std_buffer: None,
pes_extension_field: None,
..PesExtension::new()
};
let h = PesHeader {
pes_extension: Some(ext),
..empty_header()
};
let bytes = build_pes(h, &[]);
let pkt = PesPacket::parse(&bytes).unwrap();
let decoded_ext = pkt.header.unwrap().pes_extension.unwrap();
assert_eq!(decoded_ext.program_packet_sequence_counter, Some(ppsc));
}
#[test]
fn pes_extension_p_std_buffer_round_trip() {
let pstd = PStdBuffer {
scale: true,
size: 0x1FFF,
};
let ext = PesExtension {
pes_private_data: None,
pack_header: None,
program_packet_sequence_counter: None,
p_std_buffer: Some(pstd),
pes_extension_field: None,
..PesExtension::new()
};
let h = PesHeader {
pes_extension: Some(ext),
..empty_header()
};
let bytes = build_pes(h, &[]);
let pkt = PesPacket::parse(&bytes).unwrap();
let decoded_ext = pkt.header.unwrap().pes_extension.unwrap();
assert_eq!(decoded_ext.p_std_buffer, Some(pstd));
}
#[test]
fn pack_header_over_255_bytes_rejected_not_wrapped() {
let data = alloc::vec![0u8; 256];
let ext = PesExtension {
pes_private_data: None,
pack_header: Some(&data),
program_packet_sequence_counter: None,
p_std_buffer: None,
pes_extension_field: None,
..PesExtension::new()
};
let mut buf = vec![0u8; ext.serialized_len()];
assert!(ext.serialize_into(&mut buf).is_err());
}
#[test]
fn avs3_stream_id_extension_round_trips() {
for want in [
crate::STREAM_ID_EXTENSION_AVS3_AUDIO,
crate::STREAM_ID_EXTENSION_AVS3_VIDEO,
] {
let ext = PesExtension {
pes_extension_field: Some(PesExtensionField {
stream_id_extension_flag: false,
low_bits: want,
rest: &[],
}),
..PesExtension::new()
};
let mut buf = vec![0u8; ext.serialized_len()];
ext.serialize_into(&mut buf).unwrap();
let back = PesExtension::parse(&buf).unwrap();
let f = back.pes_extension_field.unwrap();
assert_eq!(f.stream_id_extension(), Some(want));
}
let tref = PesExtensionField {
stream_id_extension_flag: true,
low_bits: 1,
rest: &[],
};
assert_eq!(tref.stream_id_extension(), None);
}
#[test]
fn pes_extension_field_over_127_bytes_rejected_not_wrapped() {
let data = alloc::vec![0u8; 256];
let ext = PesExtension {
pes_private_data: None,
pack_header: None,
program_packet_sequence_counter: None,
p_std_buffer: None,
pes_extension_field: Some(PesExtensionField {
stream_id_extension_flag: false,
low_bits: 0,
rest: &data,
}),
..PesExtension::new()
};
let mut buf = vec![0u8; ext.serialized_len()];
assert!(matches!(
ext.serialize_into(&mut buf).unwrap_err(),
Error::FieldOverflow(_)
));
}
#[test]
fn p_std_buffer_size_over_13_bits_rejected_not_masked() {
let mut ext = PesExtension::new();
ext.p_std_buffer = Some(PStdBuffer {
scale: false,
size: 0x2000,
});
let mut buf = vec![0u8; ext.serialized_len()];
assert!(matches!(
ext.serialize_into(&mut buf).unwrap_err(),
Error::FieldOverflow(_)
));
ext.p_std_buffer = Some(PStdBuffer {
scale: true,
size: 0x1FFF,
});
let mut buf = vec![0u8; ext.serialized_len()];
assert!(ext.serialize_into(&mut buf).is_ok());
}
#[test]
fn pes_extension_private_data_round_trip() {
let pd: [u8; 16] = [
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
0x0F, 0x10,
];
let ext = PesExtension {
pes_private_data: Some(pd),
pack_header: None,
program_packet_sequence_counter: None,
p_std_buffer: None,
pes_extension_field: None,
..PesExtension::new()
};
let h = PesHeader {
pes_extension: Some(ext),
..empty_header()
};
let bytes = build_pes(h, &[]);
let pkt = PesPacket::parse(&bytes).unwrap();
let decoded_ext = pkt.header.unwrap().pes_extension.unwrap();
assert_eq!(decoded_ext.pes_private_data, Some(pd));
}
#[test]
fn pes_header_all_fields_round_trip() {
let ppsc = ProgramPacketSequenceCounter {
counter: 1,
mpeg1_mpeg2_identifier: false,
original_stuff_length: 0,
};
let ext = PesExtension {
pes_private_data: None,
pack_header: None,
program_packet_sequence_counter: Some(ppsc),
p_std_buffer: Some(PStdBuffer {
scale: false,
size: 100,
}),
pes_extension_field: None,
..PesExtension::new()
};
let h = PesHeader {
scrambling_control: 0,
pes_priority: true,
data_alignment_indicator: false,
copyright: false,
original_or_copy: true,
pts: Some(Pts(90_000)),
dts: Some(Dts(85_000)),
escr: Some(Escr {
base: 1000,
extension: 0,
reserved: 0b11,
}),
es_rate: Some(50_000),
es_rate_marker_bits: 0b11,
dsm_trick_mode: Some(TrickMode::SlowMotion { rep_cntrl: 3 }),
additional_copy_info: Some(AdditionalCopyInfo::new(5)),
pes_crc: Some(0xCAFE),
pes_extension: Some(ext),
header_stuffing: &[],
};
let bytes = build_pes(h, &[0xFF]);
let pkt = PesPacket::parse(&bytes).unwrap();
let dh = pkt.header.unwrap();
assert_eq!(dh.pts, Some(Pts(90_000)));
assert_eq!(dh.dts, Some(Dts(85_000)));
assert!(dh.escr.is_some());
assert_eq!(dh.es_rate, Some(50_000));
assert_eq!(
dh.dsm_trick_mode,
Some(TrickMode::SlowMotion { rep_cntrl: 3 })
);
assert_eq!(dh.additional_copy_info, Some(AdditionalCopyInfo::new(5)));
assert_eq!(dh.pes_crc, Some(0xCAFE));
assert!(dh.pes_extension.is_some());
}
}