Expand description
ETSI EN 300 468 v1.19.1 DVB Service Information parser and builder.
dvb-si turns a raw MPEG-TS into typed, decoded DVB sections and complete
logical tables: feed packets in, get back PAT/PMT/SDT/EIT/… section structs
whose text fields decode to UTF-8 and
whose descriptor loops walk into typed descriptors. Every layout is cited to
the ETSI spec and round-trip tested; the same types serialize back to bytes.
§30-second quickstart
Build a demux::SiDemux, feed it TS packets, match on
tables::AnyTableSection, walk the descriptor loop with
descriptors::DescriptorLoop::iter, and print decoded text via
text::DvbText::decode:
use dvb_si::demux::SiDemux;
use dvb_si::descriptors::AnyDescriptor;
use dvb_si::tables::AnyTableSection;
let mut demux = SiDemux::builder().build();
// In real code, `packet` is each aligned 188-byte packet from your TS source
// (file, UDP, tuner). Here we hand-build one PAT packet to keep the doctest
// self-contained — see `dvb-tools dump` (in the `dvb-tools` crate) for the
// file-reading loop.
for event in demux.feed(&packet) {
match event.table_section() {
Ok(AnyTableSection::SdtSection(sdt)) => {
for service in &sdt.services {
for item in service.descriptors.iter().flatten() {
if let AnyDescriptor::Service(svc) = item {
// DvbText decodes EN 300 468 Annex A → UTF-8.
println!("service: {}", svc.service_name.decode());
}
}
}
}
Ok(AnyTableSection::PatSection(pat)) => {
println!("PAT v{} on {}", event.version().unwrap_or(0), event.pid());
assert_eq!(pat.entries.len(), 1);
}
Ok(other) => { let _ = other; }
Err(_) => {} // malformed section
}
}
§Layer map
TS packets ─▶ demux::SiDemux ─▶ SectionEvent
│ .table_section()
▼
tables::AnyTableSection (PatSection, SdtSection, …)
│ section.<loop field> : DescriptorLoop
▼
descriptors::parse_loop ─▶ AnyDescriptor
│ field : DvbText / LangCode
▼
text::DvbText::decode() ─▶ UTF-8 String
SectionEvent.bytes() ─▶ collect::SectionSetCollector ─▶ CompleteSectionSet
│ .table::<T>()
├ .nit() / .bat() / .sdt() / .eit()
▼
complete logical tablesEach layer is independently usable: a caller who already has complete section
bytes can skip demux and call tables::AnyTableSection::parse directly; a
caller with a bare descriptor loop can call descriptors::parse_loop on it.
Use collect when a table spans multiple sections.
§RFU policy
DVB reserved-bit fields carry a semantic distinction:
reserved_future_usebits are emitted as 1 (the DVB convention that future equipment sees a “1” for unimplemented bits).reserved_zero_future_usebits are emitted as 0.
Parsers accept any value (no rejection on non-zero RFU) — unlike
dvb-t2mi, which validates RFU bits. This crate prioritises forward
compatibility with future broadcast streams.
§Features
| Feature | Default | Enables |
|---|---|---|
chrono | on | MJD + BCD time fields decode to chrono::DateTime<Utc> (EIT start_time(), TDT/TOT). Off → raw bytes. |
ts | on | demux::SiDemux, ts::SectionReassembler, TS packet parsing. Off → bring your own complete section bytes. |
serde | on | Serialize-only — for display/export (JSON via serde_json); parsing FROM JSON is deliberately unsupported, re-parse from wire bytes. Serialize on every table/descriptor; text::DvbText serializes as its decoded UTF-8 string (camelCase JSON). |
yoke | off | yoke::Yokeable on every zero-copy view type + the owned::Owned wrapper — own a parsed view past the input buffer’s borrow (store/cache/send across threads) without re-parsing or a mirror type. |
dvb-si = { version = "4.0", default-features = false } # tight, no_std-ish build§Entry points
demux::SiDemux— PID-filtered, version-gated section pump (featurets).tables::AnyTableSection/descriptors::AnyDescriptor— trait-driven dispatch on table_id / descriptor_tag;descriptors::parse_loopwalks a loop lazily.collect— generic multi-section collection plus complete NIT/BAT/SDT/EIT views with typed descriptor loops.descriptors::DescriptorRegistry— register private descriptors at runtime.descriptors::ExtensionRegistry— register private tag-extension sub-descriptors at runtime.tables::TableRegistry— register private table_ids at runtime.text::DvbText/text::LangCode— decoded-on-demand Annex A text.Parse/Serialize— the two symmetric contracts every table and descriptor implements.tables—*Sectionparsers for PAT, PMT, CAT, TSDT, NIT, BAT, SDT, EIT, TDT, TOT, RST, DIT, SIT, ST, SAT, AIT, DSM-CC section, UNT, INT, RCT, CIT, RNT, Container, MPE datagram, MPE-FEC, MPE-IFEC, protection message, downloadable font info — every allocated table_id in EN 300 468 V1.19.1 Table 2.descriptors— every DVB descriptor (tags 0x40..0x7F) plus MPEG-2 descriptors.carousel— DSM-CC data-carousel messages (DSI/DII/DDB) + module reassembly on top of thetables::dsmccsection framing.pid::well_known— reserved DVB/MPEG-2 PIDs.table_id::TableId— typed table_id enum.descriptor_tag::DescriptorTag— typed descriptor_tag enum.
See the crate README and docs/ for the structured spec reference.
MIGRATION-4.0.md covers the 3.x → 4.0 API break.
§Examples
Two runnable examples ship with this crate (cargo run -p dvb-si --example <name>).
§build_and_parse_pat
//! Basic: build a PAT section, serialize it to wire bytes, and parse it back.
//!
//! Run with: `cargo run -p dvb-si --example build_and_parse_pat`
//!
//! Shows the symmetric build/parse contract without needing a TS source —
//! every table type works the same way.
use dvb_common::{Parse, Serialize};
use dvb_si::tables::pat::{PatEntry, PatSection};
fn main() {
let pat = PatSection {
transport_stream_id: 1,
version_number: 0,
current_next_indicator: true,
section_number: 0,
last_section_number: 0,
entries: vec![
PatEntry {
program_number: 1,
pid: 0x0100,
},
PatEntry {
program_number: 2,
pid: 0x0200,
},
],
};
// Serialize to the on-wire section bytes (CRC-32 appended automatically).
let mut bytes = vec![0u8; pat.serialized_len()];
pat.serialize_into(&mut bytes).expect("buffer is sized");
println!("serialized PAT : {} bytes", bytes.len());
println!("{bytes:02X?}");
// Parse them back and prove equality (the round-trip invariant).
let parsed = PatSection::parse(&bytes).expect("valid PAT");
assert_eq!(parsed, pat);
println!("\ntransport_stream_id = {}", parsed.transport_stream_id);
for e in &parsed.entries {
println!(" program {} → PMT PID {:#06X}", e.program_number, e.pid);
}
}§list_services
//! Advanced: demux a real MPEG-TS capture and list its services and tables.
//!
//! Run with: `cargo run -p dvb-si --example list_services` (needs the default
//! `ts` feature). Reads a committed French TNT capture (which carries an SDT)
//! at runtime.
use dvb_si::demux::SiDemux;
use dvb_si::descriptors::AnyDescriptor;
use dvb_si::tables::AnyTableSection;
use std::collections::BTreeMap;
const PKT: usize = 188;
fn main() {
let path = concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/tnt-5w-12732v-isi6-10s.ts"
);
let data = match std::fs::read(path) {
Ok(b) => b,
Err(e) => {
eprintln!("fixture not available ({e}); nothing to do");
return;
}
};
let mut demux = SiDemux::builder().build();
let mut tables: BTreeMap<&'static str, u32> = BTreeMap::new();
let mut services = Vec::new();
for pkt in data.chunks(PKT) {
if pkt.len() < PKT {
break;
}
for event in demux.feed(pkt) {
match event.table_section() {
Ok(AnyTableSection::SdtSection(sdt)) => {
*tables.entry("SDT").or_default() += 1;
for service in &sdt.services {
for item in service.descriptors.iter().flatten() {
if let AnyDescriptor::Service(svc) = item {
services.push(format!(
"{} — {} (\"{}\")",
service.service_id,
svc.service_type,
svc.service_name.decode()
));
}
}
}
}
Ok(AnyTableSection::PatSection(_)) => *tables.entry("PAT").or_default() += 1,
Ok(AnyTableSection::PmtSection(_)) => *tables.entry("PMT").or_default() += 1,
Ok(AnyTableSection::NitSection(_)) => *tables.entry("NIT").or_default() += 1,
Ok(AnyTableSection::EitSection(_)) => *tables.entry("EIT").or_default() += 1,
Ok(_) => *tables.entry("other").or_default() += 1,
Err(_) => {}
}
}
}
println!("tables seen (distinct sections):");
for (name, n) in &tables {
println!(" {name:<6} {n}");
}
services.sort();
services.dedup();
println!("\nservices:");
if services.is_empty() {
println!(" (no SDT in this capture)");
}
for s in &services {
println!(" {s}");
}
}Re-exports§
pub use descriptor_tag::DescriptorTag;pub use error::Error;pub use error::Result;pub use table_id::TableId;
Modules§
- carousel
- DSM-CC data-carousel download protocol — ISO/IEC 13818-6 §7.2/§7.3 as profiled by DVB (TR 101 202 §4.6/§4.7.5, TS 102 006 SSU, TS 102 809).
- collect
- Multi-section table collection.
- compatibility
- Compatibility Descriptor — ETSI TS 102 006 §9.4.2.2 Table 15 / ISO/IEC 13818-6.
- demux
ts SiDemux— PID-filtered, version-gated SI section pump.- descriptor_
tag DescriptorTagenum — typed descriptor tag byte values.- descriptors
- DVB + MPEG-2 descriptors. Each descriptor tag gets its own submodule file.
- epg
chrono - EPG convenience layer.
- error
- Error type returned by every parser in this crate.
- mux
ts - Section → TS packetizer (the byte-exact inverse of
SectionReassembler::feed). - owned
yoke - Own a parsed view past the input buffer’s borrow (feature
yoke). - pid
- Reserved DVB/MPEG-2 PIDs.
- resync
ts - Stateful TS byte-stream resynchroniser — ISO/IEC 13818-1 §2.4.3.2.
- section
- Generic PSI/SI section framing — ETSI EN 300 468 §5.1.1.
- table_
id TableIdenum: typedtable_idbyte values.- tables
- SI + PSI table-section parsers.
- text
- DVB-SI text decoding — ETSI EN 300 468 Annex A.
- traits
- SI-specific traits.
Parseis provided bydvb_commonand imported directly at call sites. - ts
ts - MPEG-TS packet parser + section reassembler. Feature-gated under
ts.