dvb-si
DVB Service Information and MPEG-2 PSI for Rust — a typed parser and builder for every table and descriptor a DVB transport stream carries, cited clause-by-clause to the specs.
dvb-si turns the signalling inside an MPEG transport stream — the PAT/PMT that say what
programmes exist, the NIT/SDT/BAT that name networks and services, the EIT that carries the
programme guide, the TDT/TOT clock, the AIT that launches HbbTV apps, the DSM-CC carousels that
deliver files and software updates — into typed Rust values, and turns those values back into
byte-identical wire sections.
At a glance
| Section types (table_id) | 31 typed *Section parsers covering 72 table_id values — every allocated EN 300 468 Table 2 id |
| Descriptors (tag 0x00–0x7F) | 116 auto-dispatched: 52 MPEG-2 / DSM-CC (0x02–0x3F) + all 64 DVB tags (0x40–0x7F) |
| DVB extension descriptors (0x7F) | 30 typed descriptor_tag_extension bodies — every one EN 300 468 V1.20.1 Table 109 allocates |
| MPEG-2 extension descriptors (0x3F) | 24 typed extension_descriptor_tag bodies (7 MPEG-H 3D audio, plus HEVC/VVC/EVC/LCEVC/JPEG XS timing and structure, green, quality, OD update, timed metadata, media service kind) |
| AIT descriptors (TS 102 809 / MHP) | 21 typed in the separate AIT descriptor namespace |
| INT / UNT / RNT descriptors | 26 typed in their own scoped namespaces (AnyIntDescriptor, AnyUntDescriptor, AnyRntDescriptor) |
| Private / regional descriptors | 9 PDS- or registration-scoped opt-ins (NorDig, EACEM/D-Book, China GY/T + T/AI AVS2/AVS3, T/UWA) |
| Text (EN 300 468 Annex A) | 7 charset decoders covering every Table A.3 selector + Annex A.1 control codes |
| DSM-CC carousel | 12 layers: section framing, DSI/DII/DDB, module reassembly, BIOP object carousel, carousel file tree |
| Registries | 171 CA_system_id and 174 private_data_specifier names (TSDuck data, build-time) |
| Standards | 19 standards / families — see the table below |
| Known gaps | 7 allocated codes not yet typed — listed in docs/COVERAGE.md |
Standards supported
| Standard | Title | Edition implemented | Scope covered | Status |
|---|---|---|---|---|
| ETSI EN 300 468 | DVB Service Information (SI) | V1.20.1 (2026-09); RFU defaults audited back to V1.3.1 | all SI tables (NIT, BAT, SDT, EIT, TDT, TOT, RST, ST, SAT, DIT, SIT), Table 12 descriptors 0x40–0x7F, Table 109 extension descriptors, Annex A text, Annexes D/G/H/L audio descriptors | Full (98/98 allocated codes) |
| Rec. ITU-T H.222.0 | ISO/IEC 13818-1 | MPEG-2 Systems — PSI and descriptors | 08/2023 text (clause numbers in COVERAGE.md); MPEG-H 3D audio descriptors 06/2021 | PAT, CAT, PMT, TSDT; Table 2-45 descriptors; Table 2-110 extension descriptors | Partial (82/85 allocated codes) |
| ISO/IEC 13818-6 | DSM-CC (data/object carousel) | as profiled by ETSI TR 101 202 V1.2.1 (13818-6 itself is not vendored) | DSM-CC sections, DSI/DII/DDB, module reassembly, BIOP object carousel, carousel_identifier + association_tag descriptors | Partial (3/5 allocated codes) |
| ETSI EN 301 192 | DVB data broadcasting | V1.7.1 | MPE datagram, MPE-FEC, INT section; time_slice_fec_identifier + ECM_repetition_rate descriptors; DII carousel descriptors | Full (29/29 allocated codes) |
| ETSI TS 102 006 | DVB System Software Update (SSU) | V1.4.1 | UNT section, compatibilityDescriptor, SSU data_broadcast_id selector | Full (16/16 allocated codes) |
| ETSI TS 102 809 | Application signalling (HbbTV AIT) | V1.3.1 | AIT section, AIT descriptor namespace, application_signalling + service_identifier descriptors, protection message section + descriptor | Full (16/16 allocated codes) |
| ETSI TS 102 727 | DVB-MHP 1.1.3 | V1.1.1 (descriptor subset) | DVB-J application + location AIT descriptors, XAIT_location, XAIT_PID | Partial (11/12 allocated codes) |
| ETSI TS 102 323 | TV-Anytime carriage in DVB | V1.4.1 | container, RCT, CIT, RNT sections; default_authority, related_content, TVA_id, content_identifier descriptors | Full (11/11 allocated codes) |
| ETSI TS 102 772 | MPE-IFEC | V1.1.1 | MPE-IFEC section | Full (1/1 allocated codes) |
| ETSI EN 303 560 | DVB TTML subtitling | V1.1.1 | TTML_subtitling descriptor, downloadable font info section (DFIS) | Full (2/2 allocated codes) |
| ETSI TS 102 825-9 | CPCM system adaptation | V1.2.1 | cpcm_delivery_signalling extension descriptor | Full (1/1 allocated codes) |
| ETSI TS 101 154 | Audio/video coding in DVB | V2.10.1 | PMT StreamType values incl. AVS2/AVS3 (0xD2–0xD5); AVS private_data_specifier rules |
Partial |
| ETSI TS 101 162 | DVB identifiers | registry data from TSDuck (see registries/README.md) | CA_system_id and private_data_specifier value → name lookups | Partial |
| NorDig Unified Requirements | Nordic IRD requirements | v3.2.2 (2025-11) | logical channel descriptor v1 (0x83) + v2 (0x87), PDS 0x00000029 | Full (scoped) |
| EACEM / D-Book | Logical channel numbering | private (PDS 0x00000028); EACEM TR-030 is not vendored | logical_channel descriptor (0x83), 10-bit-LCN layout rests on real captures only; the vendored NorDig spec defines LCD v1 (0x83) and v2 (0x87) in §12.2.9 (both typed in nordig.rs) |
Full (scoped) |
| GY/T 270-2013 | China DTV closed captions | 2013 | caption_service_descriptor (0x86), StreamType::ChinaClosedCaption |
Full (scoped) |
| GY/T 420-2025, GY/T 422-2025 | China UHD encapsulation / multi-audio | 2025 | AVS2_video (0x40 under 'AVSV'), AVS3_video; GY/T 422 audio_preselection profile | Full (scoped) |
| T/AI 109.6-2022, T/AI 109.7-2024 | AVS3 media format / audio transport | 2022 / 2024 | AVS3_video (0xD1), AVS3_audio (0xD2), AVS2_audio (0xD3) | Full (scoped) |
| T/UWA 012.2-2023 | UWA UHD TS encapsulation | 2023 | older AVS3 video descriptor (tag 62) | Full (scoped) |
Status: Full = every code the edition allocates is typed; Partial = some are (the tally counts allocated codes, so a single untyped one shows); Full (scoped) = typed, but only decoded inside the private_data_specifier / registration scope the standard requires (opt in via DescriptorRegistry); Not implemented = bytes are preserved but not typed. Every count above is generated by tools/gen-dvbsi-coverage.py from the crate's dispatch lists and CI fails if it goes stale.
The full per-code reference — every table_id, descriptor tag and tag extension with its
standard, verified clause, Rust type and status, plus every code the standards define that is
not implemented — is docs/COVERAGE.md.
What it does — and what it does not
It does
- Parse and serialize every DVB SI table (NIT, BAT, SDT, EIT p/f + schedule, TDT, TOT, RST, ST, SAT, DIT, SIT), the MPEG-2 PSI tables (PAT, CAT, PMT, TSDT), and the DVB companion tables (AIT, UNT, INT, MPE / MPE-FEC / MPE-IFEC, container, RCT, CIT, RNT, protection message, DFIS).
- Type every descriptor those tables carry: all of EN 300 468 Table 12, the MPEG-2 systems descriptors, every EN 300 468 extension descriptor, the MPEG-H 3D audio descriptors, the AIT descriptor namespace, and opt-in regional descriptors (NorDig, EACEM/D-Book, China GY/T and T/AI AVS2/AVS3, T/UWA).
- Decode DVB text (EN 300 468 Annex A: default Latin, ISO 8859-n, UCS-2, KS X 1001, GB-2312, Big5, UTF-8) and DVB time (MJD + BCD, including the 2038 wrap rule).
- Pump sections out of a TS (
SiDemux: PID filtering, CRC check, version gating), collect multi-section tables (collect), build an EPG (epg::EpgStore), and reassemble DSM-CC data and object carousels into modules and files (carousel).
Who uses it: TS analysers and monitoring probes, set-top-box and middleware stacks, playout and multiplexers that must emit SI, EPG and channel-list tools, test-stream generators, and anything that needs to reason about a broadcast stream's signalling.
It deliberately does not
- Frame TS packets or reassemble/packetise sections itself — that is
mpeg-ts, whichdvb-siuses. - Touch elementary-stream payloads (video/audio/subtitles) — see
mpeg-pes,dvb-subtitle,dvb-vbi. - Descramble, talk to a CAM, or interpret CA-system-private ECM/EMM payloads — see
dvb-csa,dvb-ci. - Measure stream conformance (TR 101 290) — that is
dvb-conformance. - Guess. A field whose syntax lives in a standard that is not vendored stays a byte slice and is
documented as such; codes a standard allocates but the crate does not type are listed as
Not implemented in
docs/COVERAGE.md, never silently half-decoded.
Quick start
[]
= "12"
Parse one section, walk it, serialize it back
The bytes below are the PAT of a real French TNT capture (5°W, 12 732 MHz V) committed in this repository.
use ;
use PatSection;
use AnyTableSection;
Pump a whole capture: services, PMT streams, descriptors
use SiDemux;
use AnyDescriptor;
use AnyTableSection;
More: examples/build_and_parse_pat.rs,
examples/list_services.rs (cargo run -p dvb-si --example list_services), and the dvb-tools CLI
(dvb-tools dump file.ts --json) for a complete analyser built on this crate.
Which API do I use?
| You have … | Use | Notes |
|---|---|---|
| a TS file / UDP feed / tuner | demux::SiDemux → SectionEvent::table_section() |
feature ts; CRC-checked, version-gated, follows the PAT |
| complete section bytes of unknown type | tables::AnyTableSection::parse |
AnyTableSection::parse_with(&TableRegistry, …) adds private table_ids |
| complete section bytes of a known type | tables::<table>::<Name>Section::parse |
e.g. tables::sdt::SdtSection::parse |
| a table that spans several sections | collect::SectionSetCollector (.nit(), .bat(), .sdt(), .eit(), .table::<T>()) |
collect::EitCollector for EIT schedules (spans table_ids) |
| a programme guide to query | epg::EpgStore (now_and_next, schedule) |
feature chrono |
a descriptor loop (DescriptorLoop) |
.iter() → AnyDescriptor |
unknown tags surface as AnyDescriptor::Unknown, never panic |
| private / regional descriptors | descriptors::DescriptorRegistry + .iter_with(®) |
PDS-scoped: NorDig LCN, EACEM LCN, AVS2/AVS3, China captions, your own |
| private extension descriptors (0x7F) | descriptors::ExtensionRegistry + .iter_with_extensions |
|
| an AIT | tables::ait::AitSection + descriptors::ait::parse_ait_loop |
the AIT has its own descriptor namespace |
| a DSM-CC carousel | carousel::{Dsi, Dii, DownloadDataBlock, ModuleReassembler} → carousel::CarouselFs |
flate2 feature for compressed modules |
| a value to emit | build the struct (new(..)), then Serialize::serialize_into |
then mpeg-ts packetises it |
| JSON for a UI / log | the serde feature (serde_json::to_string(§ion)) |
serialize-only: re-parse from wire bytes, never from JSON |
| a parsed view to keep (cache, other thread) | owned::Owned<T> |
feature yoke; no re-parse, Send + Sync + 'static |
Private descriptors in their scope
use NordigLogicalChannelV1;
use ;
Feature flags
| Feature | Default | Enables |
|---|---|---|
std |
yes | Links std. Off → #![no_std] + alloc. The non-Latin text charsets (encoding_rs) and wall-clock time need it. |
chrono |
yes | MJD + BCD fields also as chrono::DateTime<Utc> (EIT start_time_chrono(), TDT/TOT utc_time()), local time offsets as chrono::Duration; the epg module. Off → MjdBcdDateTime and raw bytes only. |
ts |
yes | demux::SiDemux over 188-byte packets (framing from mpeg-ts). Off → bring your own section bytes. |
serde |
yes | Serialize on every table, descriptor and text type (DvbText as its decoded string). Serialize-only by design. |
yoke |
no | Yokeable on the zero-copy views + owned::Owned<T>. |
flate2 |
no | zlib-decompress compressed_module_descriptor modules in object carousels (implies std). |
= { = "12", = false } # no_std + alloc, bring your own sections
Detailed coverage
The generated tables above summarise; the per-code lists live in
docs/COVERAGE.md:
- Tables — every table_id with standard, verified clause, Rust type and status.
- Descriptors — MPEG-2 (0x00–0x3F), DVB (0x40–0x7F), DVB extension (0x7F / tag_extension),
MPEG-2 extension (0x3F / extension_descriptor_tag), private/regional (with their PDS scope and
DescriptorRegistryopt-in), AIT, INT, UNT, DSM-CC carousel and RNT-scoped descriptors. - DSM-CC carousel, Annex A text charsets, time handling and the higher layers.
- Not implemented — every code the vendored standards allocate that
dvb-sidoes not type. - Citation cross-check — module docs whose clause number disagrees with the verified edition.
Every count and row is generated by tools/gen-dvbsi-coverage.py
from the crate's dispatch lists (declare_tables!, declare_descriptors!,
declare_extension_bodies!, declare_ait_descriptors!) and module citations, joined with the
hand-transcribed allocation lists in docs/coverage/allocations.tsv.
The docs_coverage_drift test fails CI when any of it is stale. The spec transcriptions behind
every layout are indexed in docs/README.md.
Design notes
- Zero-copy parse, symmetric serialize. Every wire structure implements
broadcast_common::Parse<'a>(borrowing from the input — descriptor loops, names and selector tails are&'a [u8]/DescriptorLoop<'a>/DvbText<'a>) andbroadcast_common::Serialize(serialized_len+serialize_into). Every type has a round-trip test (parse → serialize → byte-identical; serialize → parse → equal), andtests/real_capture_byte_identity.rsre-serializes every descriptor found in the real-capture archive and requires byte identity. - Reserved bits: parse tolerant, serialize preserving. Any value of a
reserved/reserved_future_use/reserved_zero_future_userun is accepted on parse, captured in a typed, spec-named field, and re-emitted verbatim — so a sender that deviates from the edition still round-trips byte-identically, with no raw passthrough blob. Values built withnew(..)/Defaultcarry the governing edition's fill (ones forreserved, zeros forreserved_zero_future_use, documented per-field exceptions). Evidence per field:docs/reserved-bits-edition-audit.md. - Checked serializers. A value wider than its wire field is
Error::FieldOverflow, never masked into a different, valid-looking value. PIDs arempeg_ts::pid::Pid. - One dispatch list per namespace.
AnyTableSection,AnyDescriptor,ExtensionBodyandAnyAitDescriptorare generated from a single declarative list (declare_*!), with a drift test pinning each code to its type'sTABLE_ID_RANGES/TAG/TAG_EXTENSIONconst — the dispatcher cannot drift from the implemented set, and neither can these docs. - Labels. Every spec/field enum has
name()(the spec token,"reserved"for reserved arms) and aDisplaygenerated bybroadcast_common::impl_spec_display!; alabel_coveragetest enforces it. #[non_exhaustive]on public structs and enums that can grow, withnew(..)constructors, so additions are not breaking.no_std+allocwith default features off; never panics on hostile input (fuzzed; bounded collectors and EPG store).- Spec grounding. Every module's
//!doc names the standard, clause and tag; every hex literal outside tests is a named constant.
Related crates
| Crate | Relationship |
|---|---|
broadcast-common |
the Parse / Serialize traits, CRC-32/MPEG-2, MJD/BCD time, impl_spec_display! |
mpeg-ts |
TS packets, section reassembly + packetisation, PIDs — SiDemux sits on it, and it turns serialized sections back into packets |
mpeg-pes |
PES depacketisation for the elementary streams the PMT points at |
dvb-t2mi, dvb-bbframe |
the layers below the TS on DVB-T2 / S2 / S2X (lockstep releases with dvb-si) |
dvb-conformance |
ETSI TR 101 290 monitoring; uses dvb-si for its SI checks |
dvb-tools |
CLI analyser: dump / services / epg / pids / t2mi |
dvb-ci |
EN 50221 Common Interface; builds CA_PMT from a dvb-si PMT |
scte35-splice |
SCTE 35 splice_info sections (ad cueing), carried alongside SI |
dvb-si-py (bindings/python/) |
Python bindings: parse_section(bytes) -> dict, Demux |
Upgrading
MIGRATION-4.0.md (3.x → 4.0: *Section names, AnyTableSection,
table_section(), multi-section collection), MIGRATION-3.1.md,
MIGRATION-2.0.md; per-release detail in CHANGELOG.md.
MSRV and licence
MSRV 1.95.0 (the workspace rust-version; CI tests it). Licensed under either of MIT or
Apache-2.0, at your option. The vendored TSDuck registry data in registries/ is BSD-2-Clause
(see registries/README.md).