Expand description
PES (Packetized Elementary Stream) depacketization + PTS/DTS — ISO/IEC 13818-1 (Rec. ITU-T H.222.0) §2.4.3.6 / §2.4.3.7.
mpeg-pes is the sublayer between an MPEG-TS packet layer (e.g. dvb-si’s
TsPacket / SiDemux) and an elementary-stream consumer. Feed it the
payload bytes of TS packets for one PID (split on payload_unit_start), and
it yields PesPackets carrying the stream_id, presentation/decoding
timestamps (Pts/Dts, 33-bit @ 90 kHz), and the elementary-stream
bytes.
It depends only on dvb-common and is #![no_std] (+ alloc), WASM-clean.
use mpeg_pes::{PesPacket, StreamId};
// A minimal PES packet: start code, stream_id 0xE0 (video), len, header, PTS, payload.
let bytes = [
0x00, 0x00, 0x01, 0xE0, 0x00, 0x0A, 0x80, 0x80, 0x05,
0x21, 0x00, 0x01, 0x00, 0x01, // PTS = 0
0xAA, 0xBB, // ES payload
];
let pkt = PesPacket::parse(&bytes).unwrap();
assert_eq!(pkt.stream_id, StreamId(0xE0));
assert!(pkt.header.as_ref().unwrap().pts.is_some());
assert_eq!(pkt.payload, &[0xAA, 0xBB]);§Examples
Two runnable examples ship with this crate (cargo run -p mpeg-pes --example <name>).
§parse_pes_packet
ⓘ
//! Basic: parse one PES packet from raw bytes and read its stream_id + PTS.
//!
//! Run with: `cargo run -p mpeg-pes --example parse_pes_packet`
use mpeg_pes::{PesPacket, StreamId};
fn main() {
// A minimal PES packet: start_code(00 00 01), stream_id 0xE0 (video),
// PES_packet_length, flags, PES_header_data_length, a 5-byte PTS, then payload.
let bytes = [
0x00, 0x00, 0x01, 0xE0, // packet_start_code_prefix + stream_id
0x00, 0x0A, // PES_packet_length = 10
0x80, 0x80, 0x05, // '10' marker, PTS_DTS_flags=10, PES_header_data_length=5
0x21, 0x00, 0x01, 0x00, 0x01, // PTS = 0 (33-bit, 90 kHz)
0xAA, 0xBB, // elementary-stream payload
];
let pkt = PesPacket::parse(&bytes).expect("valid PES packet");
println!("stream_id : {:#04X}", pkt.stream_id.0);
println!("is_video : {}", pkt.stream_id == StreamId(0xE0));
println!("packet_length : {}", pkt.pes_packet_length);
let header = pkt
.header
.as_ref()
.expect("video PES carries an optional header");
match header.pts {
Some(pts) => println!(
"PTS : {} ticks ({:.6}s)",
pts.ticks(),
pts.seconds()
),
None => println!("PTS : (none)"),
}
println!("payload : {:02X?}", pkt.payload);
}§extract_pts
ⓘ
//! Advanced: depacketize a real MPEG-TS capture, reassemble PES on one PID,
//! and report the PTS timeline.
//!
//! Run with: `cargo run -p mpeg-pes --example extract_pts`
//!
//! Reads the committed `m6-single.ts` DVB fixture from the sibling `dvb-si`
//! crate at runtime (so the example compiles even when the fixture is absent).
use mpeg_pes::{PesAssembler, PesPacket};
const PKT: usize = 188;
const PES_PID: u16 = 0x0082; // an ES PID carrying PES with PTS in this capture
/// (pid, payload_unit_start, payload) for a TS packet that carries payload.
fn ts_payload(p: &[u8]) -> Option<(u16, bool, &[u8])> {
if p.len() != PKT || p[0] != 0x47 {
return None;
}
let pid = (u16::from(p[1] & 0x1F) << 8) | u16::from(p[2]);
let pusi = p[1] & 0x40 != 0;
let start = match (p[3] >> 4) & 0x03 {
1 => 4, // payload only
3 => 5 + usize::from(p[4]), // adaptation field, then payload
_ => return None, // 0 reserved / 2 adaptation only
};
(start < PKT).then(|| (pid, pusi, &p[start..]))
}
fn main() {
let path = concat!(
env!("CARGO_MANIFEST_DIR"),
"/../dvb-si/tests/fixtures/m6-single.ts"
);
let ts = match std::fs::read(path) {
Ok(b) => b,
Err(e) => {
eprintln!("fixture not available ({e}); nothing to do");
return;
}
};
let mut asm = PesAssembler::new();
let mut pes = Vec::new();
for pkt in ts.chunks(PKT) {
if let Some((pid, pusi, payload)) = ts_payload(pkt) {
if pid == PES_PID {
if let Some(v) = asm.feed(pusi, payload) {
pes.push(v);
}
}
}
}
if let Some(v) = asm.flush() {
pes.push(v);
}
let mut pts_ticks = Vec::new();
for raw in &pes {
let pkt = PesPacket::parse(raw).expect("captured PES must parse");
if let Some(pts) = pkt.header.as_ref().and_then(|h| h.pts) {
assert!(pts.ticks() < (1 << 33), "PTS must be 33-bit");
pts_ticks.push(pts.ticks());
}
}
println!(
"PID {PES_PID:#06X}: {} PES packets, {} with PTS",
pes.len(),
pts_ticks.len()
);
if let (Some(&min), Some(&max)) = (pts_ticks.iter().min(), pts_ticks.iter().max()) {
let span = (max - min) as f64 / 90_000.0;
println!("PTS range : {min}..={max} ticks (~{span:.3}s span @ 90 kHz)");
}
}Structs§
- Dts
- Decoding Time Stamp (33-bit, 90 kHz units).
- PesAssembler
- Reassembles PES packets for a single PID from successive TS payloads.
- PesHeader
- The optional PES header present for non-special
stream_ids (§2.4.3.6). The variable optional fields (PTS/DTS, ESCR, ES_rate, trick mode, …) are retained verbatim inoptional_fields;pts/dtsare decoded from their front for convenience. - PesPacket
- A parsed PES packet.
- Pts
- Presentation Time Stamp (33-bit, 90 kHz units).
- Stream
Id - 8-bit
stream_idof a PES packet.
Enums§
- Error
- A PES parse error.
Constants§
- PACKET_
START_ CODE_ PREFIX - The 3-byte
packet_start_code_prefixthat opens every PES packet (0x000001).
Type Aliases§
- Result
- Result alias for PES parsing.