mpeg-ts
MPEG-2 Transport Stream framing: TS packets, adaptation fields, PCR, PSI section reassembly and packetisation, byte-stream resync.
What it does / what it does not
mpeg-ts is the lowest layer of the workspace's broadcast stack. It turns a
stream of bytes into typed 188-byte transport packets (and back), and it turns
the payload of PSI/SI PIDs into complete sections (and back). It is for anyone
who needs raw TS framing without the SI/descriptor stack: demuxers, muxers,
stream repair tools, analyzers, embedded firmware.
It does:
- parse and serialize the 4-byte TS header and the adaptation field (every bit, including stuffing and reserved bits, so parse -> serialize is byte-exact on real streams);
- reassemble PSI/SI sections from TS payloads (
pointer_field, multi-packet, several sections per packet) and packetise sections back, with continuity counters and0xFFstuffing; - schedule repeating SI tables into a TS packet stream (
SiMux); - recover packet alignment from a raw byte stream (188- and 204-byte strides);
- accumulate PUSI-delimited payloads on non-PSI PIDs (e.g. DASH
emsg); - provide an owned, mutable packet type for in-place edits (continuity counter, PCR).
It deliberately does not: parse PES (see mpeg-pes), interpret PSI/SI table
bodies or descriptors (see dvb-si), descramble (see dvb-csa), check
stream conformance (see dvb-conformance), or do any I/O.
Standards supported
| Standard | Edition | Scope | Status | Notes |
|---|---|---|---|---|
| ITU-T H.222.0 / ISO/IEC 13818-1, Generic coding of moving pictures and associated audio information: Systems | H.222.0 v9 (08/2023); vendored copy verified in private/specs-md |
§2.4.3.2 TS packet layer, §2.4.3.3 field semantics, §2.4.3.4 adaptation field (incl. PCR/OPCR, splice, LTW, piecewise rate, extension), §2.4.4 PSI pointer_field and section framing |
Full for the listed clauses | Other H.222.0 clauses (PES §2.4.3.6, program streams §2.5, descriptors) are not this crate's job. Layouts cited by clause in module docs; 2007-era layouts for these clauses are unchanged. |
| ETSI EN 300 468 | V1.19.1 (the edition vendored; a V1.20.1 copy is also present) | §5.1.1 generic section header, §5.1.3 Table 1 well-known PIDs, CRC_32 placement | Partial: framing and PID constants only | Table bodies and descriptors live in dvb-si. The TOT's CRC is not handled by the short-form path (use dvb-si). |
| ISO/IEC 23009-1 | not pinned by this crate | §5.10.3.3.5 PUSI-delimited emsg carriage on PID 0x0004 |
Partial: generic PUSI reassembly only | The emsg box itself is parsed by mp4-emsg. |
| 204-byte RS-coded packets (188 + 16 parity) | n/a | Stride detection in TsResync |
Partial | Parity bytes are stripped, never checked. |
Feature flags
| Feature | Default | What it enables |
|---|---|---|
std |
yes | std::error::Error impls; turn off for no_std |
serde |
no | serde::Serialize on packet/section types (serialize-only) |
serde is opt-in and off by default; enable it explicitly:
= { = "0.7", = ["serde"] }
The crate is #![no_std] + alloc and #![forbid(unsafe_code)];
default-features = false builds on bare-metal targets that have a heap.
[]
= "0.7"
# embedded:
# mpeg-ts = { version = "0.5", default-features = false }
Quick start
Parse a real PAT packet (extracted from the committed m6-single.ts fixture)
and recover its section:
use ;
use well_known;
// PID 0x0000, PUSI set, payload-only; PAT section: transport_stream_id 1, one program.
let mut raw = ;
raw.copy_from_slice;
let pkt = parse?;
assert_eq!;
assert!;
let mut reasm = default;
reasm.feed;
let section = reasm.pop_section.expect;
assert_eq!; // table_id = PAT
assert_eq!; // transport_stream_id
# Ok::
Build and edit packets without manual byte twiddling:
use ;
use ;
let null = null_packet;
assert_eq!;
let mut raw = serialize_with_payload?;
set_continuity_counter;
assert_eq!;
let mut pcr_pkt = ;
pcr_pkt = 0x20; // adaptation only
pcr_pkt = 7; // adaptation_field_length
pcr_pkt = 0x10; // PCR flag
set_pcr?;
let parsed = parse?;
assert_eq!;
# Ok::
Runnable examples: cargo run -p mpeg-ts --example demux, --example iter_packets,
--example edit_packet.
What is implemented
| Module | Public API | Purpose |
|---|---|---|
ts |
TsPacket<'a>, TsHeader, AdaptationField<'a>, AdaptationFieldExtension<'a>, Pcr, Ltw, SeamlessSplice, ScramblingControl, AdaptationFieldControl, SectionReassembler, iter_packets, extract_ts_payload, TS_PACKET_SIZE |
Zero-copy packet view, bit-exact adaptation field, PCR (27 MHz) and per-PID PSI section reassembly |
owned |
OwnedTsPacket |
Owned 188-byte packet: build (null_packet, serialize_with_payload), mutate (set_continuity_counter, set_pcr, payload_mut), inspect |
pid |
Pid, PidOutOfRange, well_known::{PAT, CAT, TSDT, IPMP_CIT, NIT, SDT_BAT, EIT, RST, TDT_TOT, NETWORK_SYNC, RNT, SAT, MEASUREMENT, DIT, SIT, ATSC_PSIP, NULL} |
Typed 13-bit PID newtype |
section |
Section<'a> |
Generic section header (table_id, syntax indicator, length, version, section numbers, CRC validation) |
mux |
SectionPacketiser, SiMux (upsert, upsert_pmt, upsert_sdt_actual, upsert_tdt, upsert_tot, poll, poll_into) |
Sections to TS packets with continuity and stuffing; interval-scheduled SI repetition |
resync |
TsResync, PacketStride (Ts188, Rs204), ResyncStats |
Alignment recovery from raw bytes; feed_into appends to a caller-owned vector. Drain take_losses() regularly: the post-lock loss list is unbounded and only that call empties it |
pusi |
PusiReassembler, DEFAULT_MAX_UNIT_SIZE |
PUSI-delimited unit accumulation for non-PSI PIDs, size-capped |
error |
Error, Result |
Structured thiserror errors (BufferTooShort, CrcMismatch, InvalidSyncByte, OutputBufferTooSmall, FieldOverflow, ...) |
Typed PIDs: Pid is used everywhere (TsHeader::pid, the mux types,
PusiReassembler, OwnedTsPacket). Pid::try_new / TryFrom<u16> reject
values above 0x1FFF; Pid::new / Pid::from_wire_masked mask bits already
taken from the wire; there is deliberately no From<u16>. Pid implements
UpperHex/LowerHex and is serde(transparent).
Design notes
- Zero-copy, symmetric. Parsed types borrow the input; every wire type has
a matching serializer.
tests/it/fixture_roundtrip.rsandtests/it/af_extension_roundtrip.rsre-serialize every adaptation field of every committed.tsfixture and require byte-identical output. - No information dropped. Adaptation-field stuffing,
pcr_reserved/opcr_reserved, splice marker bits and every extension reserved bit are carried; the structs are#[non_exhaustive]withnew()/Default(reserved bits default to1, per the spec convention). - Checked widths. Serializers use
broadcast_common::lenchecked helpers (FieldOverflow), never a silent mask. - Shared clock arithmetic. PCR and the marker-bit timestamp come from
broadcast_common::clock33. - Label convention. Public field enums expose
name()andDisplay(workspace-guards/tests/guards/label.rsguards it).
Related crates
Depends on broadcast-common. Built on by dvb-si (tables/descriptors),
mpeg-pes, dvb-stream, dvb-conformance, ts-fix, media-doctor and
transmux. Spec citations: docs/.
Users migrating from dvb-si <= 7.x: ts, mux, resync and pid moved here
unchanged in name.
MSRV 1.95.0. Licensed under MIT or Apache-2.0, at your option.