const MAX_FRAME_SIZE: usize = 256 * 1024 * 1024;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct SourcePos {
pub sector: u64,
pub byte: u64,
}
impl SourcePos {
pub fn at_byte(byte: u64) -> Self {
Self {
sector: byte / 2048,
byte,
}
}
}
#[derive(Debug, Clone)]
pub struct PesFrame {
pub track: usize,
pub pts: i64,
pub keyframe: bool,
pub data: Vec<u8>,
pub duration_ns: Option<u64>,
pub source: Option<SourcePos>,
pub coding: Option<crate::mux::codec::PictureInfo>,
}
const DURATION_NONE_SENTINEL: u64 = u64::MAX;
impl PesFrame {
pub fn serialize(&self, w: &mut dyn std::io::Write) -> std::io::Result<()> {
if self.track > 255 {
return Err(crate::error::Error::PesTrackTooLarge { track: self.track }.into());
}
if self.data.len() > MAX_FRAME_SIZE {
return Err(crate::error::Error::PesFrameTooLarge {
size: self.data.len(),
}
.into());
}
let duration_wire = self.duration_ns.unwrap_or(DURATION_NONE_SENTINEL);
w.write_all(&[self.track as u8])?;
w.write_all(&self.pts.to_le_bytes())?;
w.write_all(&[if self.keyframe { 1 } else { 0 }])?;
w.write_all(&duration_wire.to_le_bytes())?;
w.write_all(&(self.data.len() as u32).to_le_bytes())?;
w.write_all(&self.data)
}
pub fn deserialize(r: &mut dyn std::io::Read) -> std::io::Result<Option<Self>> {
let mut first = [0u8; 1];
loop {
match r.read(&mut first) {
Ok(0) => return Ok(None), Ok(_) => break,
Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
}
}
let mut header = [0u8; 22]; header[0] = first[0];
r.read_exact(&mut header[1..])?;
let track = header[0] as usize;
let pts = i64::from_le_bytes([
header[1], header[2], header[3], header[4], header[5], header[6], header[7], header[8],
]);
let keyframe = header[9] != 0;
let duration_wire = u64::from_le_bytes([
header[10], header[11], header[12], header[13], header[14], header[15], header[16],
header[17],
]);
let duration_ns = if duration_wire == DURATION_NONE_SENTINEL {
None
} else {
Some(duration_wire)
};
let len = u32::from_le_bytes([header[18], header[19], header[20], header[21]]) as usize;
if len > MAX_FRAME_SIZE {
return Err(crate::error::Error::PesFrameTooLarge { size: len }.into());
}
let mut data = vec![0u8; len];
r.read_exact(&mut data)?;
Ok(Some(Self {
track,
pts,
keyframe,
data,
duration_ns,
source: None,
coding: None,
}))
}
pub(crate) fn from_codec_frame(track: usize, frame: crate::mux::codec::Frame) -> Self {
Self {
track,
pts: frame.pts_ns,
keyframe: frame.keyframe,
data: frame.data,
duration_ns: frame.duration_ns,
source: frame.source,
coding: frame.coding,
}
}
}
pub trait Stream: Send {
fn read(&mut self) -> std::io::Result<Option<PesFrame>>;
fn write(&mut self, frame: &PesFrame) -> std::io::Result<()>;
fn finish(&mut self) -> std::io::Result<()>;
fn info(&self) -> &crate::disc::DiscTitle;
fn codec_private(&self, _track: usize) -> Option<Vec<u8>> {
None
}
fn headers_ready(&self) -> bool {
true
}
fn errors(&self) -> u64 {
0
}
fn lost_bytes(&self) -> u64 {
0
}
}
pub struct CountingStream {
inner: Box<dyn Stream>,
written: u64,
}
impl CountingStream {
pub fn new(inner: Box<dyn Stream>) -> Self {
Self { inner, written: 0 }
}
pub fn bytes_written(&self) -> u64 {
self.written
}
}
impl Stream for CountingStream {
fn read(&mut self) -> std::io::Result<Option<PesFrame>> {
self.inner.read()
}
fn write(&mut self, frame: &PesFrame) -> std::io::Result<()> {
self.inner.write(frame)?;
self.written += frame.data.len() as u64;
Ok(())
}
fn finish(&mut self) -> std::io::Result<()> {
self.inner.finish()
}
fn info(&self) -> &crate::disc::DiscTitle {
self.inner.info()
}
fn codec_private(&self, track: usize) -> Option<Vec<u8>> {
self.inner.codec_private(track)
}
fn headers_ready(&self) -> bool {
self.inner.headers_ready()
}
fn errors(&self) -> u64 {
self.inner.errors()
}
fn lost_bytes(&self) -> u64 {
self.inner.lost_bytes()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::disc::DiscTitle;
fn make_frame(track: usize, pts: i64) -> PesFrame {
PesFrame {
coding: None,
source: None,
track,
pts,
keyframe: track == 0 && pts == 0,
data: vec![track as u8, (pts & 0xff) as u8, 0xAA],
duration_ns: None,
}
}
struct MockStream {
read_queue: std::vec::IntoIter<PesFrame>,
written: Vec<PesFrame>,
title: DiscTitle,
}
impl MockStream {
fn new(read_frames: Vec<PesFrame>) -> Self {
Self {
read_queue: read_frames.into_iter(),
written: Vec::new(),
title: DiscTitle::empty(),
}
}
}
impl Stream for MockStream {
fn read(&mut self) -> std::io::Result<Option<PesFrame>> {
Ok(self.read_queue.next())
}
fn write(&mut self, frame: &PesFrame) -> std::io::Result<()> {
self.written.push(frame.clone());
Ok(())
}
fn finish(&mut self) -> std::io::Result<()> {
Ok(())
}
fn info(&self) -> &DiscTitle {
&self.title
}
}
#[test]
fn stream_read_yields_frames_then_eof() {
let frames = vec![make_frame(0, 0), make_frame(1, 1_000), make_frame(0, 2_000)];
let mut s = MockStream::new(frames.clone());
let f0 = s.read().unwrap().expect("first frame");
assert_eq!(f0.track, frames[0].track);
assert_eq!(f0.pts, frames[0].pts);
assert!(f0.keyframe);
let f1 = s.read().unwrap().expect("second frame");
assert_eq!(f1.pts, frames[1].pts);
let f2 = s.read().unwrap().expect("third frame");
assert_eq!(f2.pts, frames[2].pts);
assert!(s.read().unwrap().is_none());
assert!(s.read().unwrap().is_none()); }
#[test]
fn stream_write_collects_then_finishes() {
let mut s = MockStream::new(Vec::new());
let frames = [make_frame(0, 0), make_frame(1, 100), make_frame(2, 200)];
for f in &frames {
s.write(f).unwrap();
}
assert_eq!(s.written.len(), 3);
s.finish().unwrap();
}
#[test]
fn stream_via_dyn_object() {
let mut s: Box<dyn Stream> = Box::new(MockStream::new(vec![make_frame(0, 0)]));
let frame = s.read().unwrap().expect("first frame");
s.write(&frame).unwrap();
let _ = s.info();
s.finish().unwrap();
}
#[test]
fn frame_roundtrips_through_bytes() {
let frame = make_frame(3, 123_456);
let mut buf = Vec::new();
frame.serialize(&mut buf).expect("serialize");
let mut cursor = std::io::Cursor::new(buf);
let got = PesFrame::deserialize(&mut cursor)
.expect("deserialize")
.expect("frame present");
assert_eq!(got.track, frame.track);
assert_eq!(got.pts, frame.pts);
assert_eq!(got.keyframe, frame.keyframe);
assert_eq!(got.data, frame.data);
assert!(PesFrame::deserialize(&mut cursor).unwrap().is_none());
}
#[test]
fn empty_input_is_clean_eof() {
let mut cursor = std::io::Cursor::new(Vec::new());
assert!(PesFrame::deserialize(&mut cursor).unwrap().is_none());
}
#[test]
fn truncated_header_is_error_not_eof() {
let mut cursor = std::io::Cursor::new(vec![1u8, 2, 3, 4, 5]);
let err = PesFrame::deserialize(&mut cursor).expect_err("partial header must error");
assert_eq!(err.kind(), std::io::ErrorKind::UnexpectedEof);
}
#[test]
fn oversize_track_rejected_on_serialize() {
let frame = make_frame(256, 0);
let mut buf = Vec::new();
let err = frame
.serialize(&mut buf)
.expect_err("track > 255 must fail");
let code = format!("E{}", crate::error::E_PES_TRACK_TOO_LARGE);
assert!(err.to_string().contains(&code), "got: {err}");
}
struct FailingWriteStream {
title: DiscTitle,
}
impl Stream for FailingWriteStream {
fn read(&mut self) -> std::io::Result<Option<PesFrame>> {
Ok(None)
}
fn write(&mut self, _frame: &PesFrame) -> std::io::Result<()> {
Err(std::io::Error::from(std::io::ErrorKind::BrokenPipe))
}
fn finish(&mut self) -> std::io::Result<()> {
Ok(())
}
fn info(&self) -> &DiscTitle {
&self.title
}
}
#[test]
fn counting_stream_does_not_count_failed_writes() {
let mut cs = CountingStream::new(Box::new(FailingWriteStream {
title: DiscTitle::empty(),
}));
let frame = make_frame(0, 0);
assert!(cs.write(&frame).is_err());
assert_eq!(cs.bytes_written(), 0);
}
#[test]
fn counting_stream_counts_successful_writes() {
let frame = make_frame(0, 0);
let payload = frame.data.len() as u64;
let mut cs = CountingStream::new(Box::new(MockStream::new(Vec::new())));
cs.write(&frame).unwrap();
assert_eq!(cs.bytes_written(), payload);
}
#[test]
fn serialize_wire_format_matches_spec() {
let frame = PesFrame {
coding: None,
source: None,
track: 2,
pts: 0x0102030405060708_i64,
keyframe: true,
data: vec![0xAA, 0xBB, 0xCC],
duration_ns: Some(0xDEADBEEF_u64),
};
let mut buf = Vec::new();
frame.serialize(&mut buf).unwrap();
assert_eq!(buf[0], 2, "byte 0 must be track");
let pts_bytes = 0x0102030405060708_i64.to_le_bytes();
assert_eq!(
&buf[1..9],
&pts_bytes,
"bytes 1..9 must be pts in little-endian"
);
assert_eq!(buf[9], 1, "byte 9 must be 1 for keyframe=true");
let dur_bytes = 0xDEADBEEF_u64.to_le_bytes();
assert_eq!(
&buf[10..18],
&dur_bytes,
"bytes 10..18 must be duration_ns in little-endian"
);
let len_bytes = 3_u32.to_le_bytes();
assert_eq!(
&buf[18..22],
&len_bytes,
"bytes 18..22 must be data length LE u32"
);
assert_eq!(
&buf[22..],
&[0xAA, 0xBB, 0xCC],
"data must follow header verbatim"
);
}
#[test]
fn serialize_keyframe_false_encodes_as_zero() {
let frame = PesFrame {
coding: None,
source: None,
track: 0,
pts: 0,
keyframe: false,
data: vec![1],
duration_ns: None,
};
let mut buf = Vec::new();
frame.serialize(&mut buf).unwrap();
assert_eq!(
buf[9], 0,
"keyframe=false must encode as 0 at wire offset 9"
);
}
#[test]
fn serialize_track_255_is_ok_track_256_is_err() {
let ok_frame = PesFrame {
coding: None,
source: None,
track: 255,
pts: 0,
keyframe: false,
data: vec![],
duration_ns: None,
};
let mut buf = Vec::new();
ok_frame.serialize(&mut buf).unwrap();
assert_eq!(buf[0], 255, "track 255 must serialize to 0xFF");
let too_large = PesFrame {
coding: None,
source: None,
track: 256,
pts: 0,
keyframe: false,
data: vec![],
duration_ns: None,
};
let mut buf2 = Vec::new();
assert!(
too_large.serialize(&mut buf2).is_err(),
"track 256 must be rejected"
);
}
#[test]
fn deserialize_round_trips_pts_boundaries() {
for pts in [0_i64, i64::MAX, i64::MIN] {
let frame = PesFrame {
coding: None,
source: None,
track: 0,
pts,
keyframe: false,
data: vec![1],
duration_ns: None,
};
let mut buf = Vec::new();
frame.serialize(&mut buf).unwrap();
let mut cursor = std::io::Cursor::new(buf);
let got = PesFrame::deserialize(&mut cursor).unwrap().unwrap();
assert_eq!(got.pts, pts, "pts={pts} must survive round-trip");
}
}
#[test]
fn deserialize_accepts_zero_length_data() {
let frame = PesFrame {
coding: None,
source: None,
track: 3,
pts: 99,
keyframe: false,
data: vec![],
duration_ns: None,
};
let mut buf = Vec::new();
frame.serialize(&mut buf).unwrap();
let mut cursor = std::io::Cursor::new(buf);
let got = PesFrame::deserialize(&mut cursor).unwrap().unwrap();
assert_eq!(got.track, 3);
assert!(
got.data.is_empty(),
"zero-length data must round-trip as empty"
);
}
#[test]
fn deserialize_duration_ns_roundtrips() {
let frame_none = PesFrame {
coding: None,
source: None,
track: 0,
pts: 0,
keyframe: false,
data: vec![1, 2, 3],
duration_ns: None,
};
let mut buf = Vec::new();
frame_none.serialize(&mut buf).unwrap();
let mut cursor = std::io::Cursor::new(buf);
let got = PesFrame::deserialize(&mut cursor).unwrap().unwrap();
assert!(
got.duration_ns.is_none(),
"None duration_ns must round-trip as None"
);
let frame_zero = PesFrame {
coding: None,
source: None,
track: 1,
pts: 1000,
keyframe: false,
data: vec![4, 5],
duration_ns: Some(0),
};
let mut buf2 = Vec::new();
frame_zero.serialize(&mut buf2).unwrap();
let mut cursor2 = std::io::Cursor::new(buf2);
let got2 = PesFrame::deserialize(&mut cursor2).unwrap().unwrap();
assert_eq!(
got2.duration_ns,
Some(0),
"Some(0) duration_ns must round-trip as Some(0)"
);
let frame_n = PesFrame {
coding: None,
source: None,
track: 2,
pts: 5_000_000_000,
keyframe: false,
data: vec![6],
duration_ns: Some(3_000_000_000),
};
let mut buf3 = Vec::new();
frame_n.serialize(&mut buf3).unwrap();
let mut cursor3 = std::io::Cursor::new(buf3);
let got3 = PesFrame::deserialize(&mut cursor3).unwrap().unwrap();
assert_eq!(
got3.duration_ns,
Some(3_000_000_000),
"Some(3_000_000_000) duration_ns must round-trip"
);
}
#[test]
fn deserialize_two_sequential_frames() {
let f1 = PesFrame {
coding: None,
source: None,
track: 0,
pts: 100,
keyframe: true,
data: vec![1, 2],
duration_ns: None,
};
let f2 = PesFrame {
coding: None,
source: None,
track: 1,
pts: 200,
keyframe: false,
data: vec![3, 4, 5],
duration_ns: None,
};
let mut buf = Vec::new();
f1.serialize(&mut buf).unwrap();
f2.serialize(&mut buf).unwrap();
let mut cursor = std::io::Cursor::new(buf);
let got1 = PesFrame::deserialize(&mut cursor).unwrap().unwrap();
let got2 = PesFrame::deserialize(&mut cursor).unwrap().unwrap();
assert_eq!(got1.track, 0);
assert_eq!(got1.pts, 100);
assert_eq!(got1.data, vec![1, 2]);
assert_eq!(got2.track, 1);
assert_eq!(got2.pts, 200);
assert_eq!(got2.data, vec![3, 4, 5]);
assert!(PesFrame::deserialize(&mut cursor).unwrap().is_none());
}
#[test]
fn counting_stream_accumulates_across_multiple_writes() {
let f1 = PesFrame {
coding: None,
source: None,
track: 0,
pts: 0,
keyframe: false,
data: vec![1, 2, 3],
duration_ns: None,
};
let f2 = PesFrame {
coding: None,
source: None,
track: 0,
pts: 1,
keyframe: false,
data: vec![4, 5],
duration_ns: None,
};
let mut cs = CountingStream::new(Box::new(MockStream::new(Vec::new())));
cs.write(&f1).unwrap();
cs.write(&f2).unwrap();
assert_eq!(cs.bytes_written(), 5, "must accumulate 3+2=5 bytes");
}
}