Expand description
VBI data carriage in DVB — ETSI EN 301 775 V1.2.1 §4 (the PES data field).
EN 301 775 specifies how Vertical Blanking Information (VBI) is carried in
MPEG-2 / DVB Transport Streams using the private PES packet mechanism
(stream_id = private_stream_1 0xBD). It extends EN 300 472 (EBU Teletext
carriage) with Inverted Teletext, VPS (EN 300 231), WSS
(EN 300 294), Closed Captioning (line 21, EIA-608 Rev A), and a generic
monochrome 4:2:2 luminance-sample transport.
This crate decodes the PES data field (DataField, §4.4.1, Table 1):
a DataField::data_identifier byte (Table 2) followed by a loop of
DataUnits. Each data unit is a DataUnitId (Table 3) + an 8-bit
data_unit_length + a typed DataUnitPayload:
TeletextDataField— EBU (0x02/0x03) and Inverted (0xC0) Teletext (§4.5): a sharedLineHeader+ an 8-bitframing_code+ a 42-byte opaquetxt_data_block. EN 300 706 Teletext coding is out of scope.VpsDataField— VPS (0xC3, §4.6): shared header + 13-byte block.WssDataField— WSS (0xC4, §4.7): shared header + a 14-bitwss_data_block+ a 2-bitreserved_future_use11tail.ClosedCaptioningDataField— Closed Captioning (0xC5, §4.8): shared header + a 16-bit data block.MonochromeDataField— monochrome 4:2:2 samples (0xC6, §4.9): its own first-byte packing (first/last segment flags + field_parity + line_offset), afirst_pixel_position,n_pixels, and the luminanceY_valuebytes.- Stuffing (
0xFF, §4.4.1) and anOpaquecatch-all for reserved / user-defined ids (Table 3: discard) round-trip verbatim.
⚠ Table 1’s parse branch routes data_unit_id 0xC1 to txt_data_field(),
but Table 3 marks 0xC1 as reserved → discard. Table 3 is authoritative,
so 0xC1 decodes to DataUnitId::Reserved (see docs/vbi.md).
No raw passthrough: every typed field re-serializes from its parsed value,
data_unit_length is recomputed from the typed body on serialize, and a
committed fixture is byte-exact round-tripped in the crate’s tests.
#![no_std] + alloc; depends only on broadcast-common.
§Examples
Build a multi-unit VBI PES data field (VPS + WSS) from typed fields and round-trip it:
use dvb_vbi::{DataField, DataUnit, LineHeader, VpsDataField, WssDataField};
let vps = DataUnit::vps(VpsDataField {
header: LineHeader::new(true, 16),
vps_data_block: [0u8; 13],
});
let wss = DataUnit::wss(WssDataField {
header: LineHeader::new(true, 23),
wss_data_block: 0x1234,
});
let field = DataField::new(0x10, vec![vps, wss]);
let mut buf = vec![0u8; field.serialized_len()];
field.serialize_into(&mut buf).unwrap();
assert_eq!(DataField::parse(&buf).unwrap(), field);§Runnable examples
Run with cargo run -p dvb-vbi --example <name>.
§build_data_field
/// Build a VBI PES data field (ETSI EN 301 775 §4.4) from typed fields —
/// a VPS unit, a WSS unit, and an EBU Teletext unit — serialize it (recomputing
/// each `data_unit_length`), and dump the wire bytes.
///
/// ```sh
/// cargo run -p dvb-vbi --example build_data_field
/// ```
use dvb_vbi::{
DataField, DataUnit, FRAMING_CODE_EBU, LineHeader, TeletextDataField, VpsDataField,
WssDataField,
};
fn main() {
// VPS on line 16, first field.
let vps = DataUnit::vps(VpsDataField {
header: LineHeader::new(true, 16),
vps_data_block: [
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
],
});
// WSS on line 23, first field, 14-bit payload 0x1234.
let wss = DataUnit::wss(WssDataField {
header: LineHeader::new(true, 23),
wss_data_block: 0x1234,
});
// EBU Teletext (non-subtitle) on line 7, second field.
let txt = DataUnit::teletext(
dvb_vbi::DataUnitId::EbuTeletextNonSubtitle,
TeletextDataField {
header: LineHeader::new(false, 7),
framing_code: FRAMING_CODE_EBU,
txt_data_block: [0xAB; 42],
},
);
// data_identifier 0x10 (EBU Teletext combined with VPS / WSS / ...).
let field = DataField::new(0x10, vec![vps, wss, txt]);
let mut bytes = vec![0u8; field.serialized_len()];
let n = field.serialize_into(&mut bytes).unwrap();
println!("PES data field: {n} bytes");
println!("data_identifier: 0x{:02X}", field.data_identifier);
println!("data units: {}", field.data_units.len());
for u in &field.data_units {
println!(
" {} (id 0x{:02X}), data_unit_length {}",
u.id,
u.id.to_u8(),
u.data_unit_length()
);
}
print!("wire bytes:");
for b in &bytes {
print!(" {b:02X}");
}
println!();
// Round-trip sanity.
assert_eq!(DataField::parse(&bytes).unwrap(), field);
println!("round-trip: OK");
}§parse_data_field
/// Read the committed VBI PES data-field fixture, parse it into typed data
/// units, print a summary, and confirm a byte-exact round-trip.
///
/// ```sh
/// cargo run -p dvb-vbi --example parse_data_field
/// ```
use std::fs;
use dvb_vbi::{DataField, DataUnitPayload};
fn main() {
// Fixtures live in the workspace-shared `fixtures/` tree, not under the
// crate. A committed fixture that cannot be read is a bug, not a reason
// to skip — so a missing/unreadable fixture is a hard failure.
let path = concat!(
env!("CARGO_MANIFEST_DIR"),
"/../fixtures/dvb-vbi/vbi_data_field.bin"
);
let bytes = fs::read(path)
.unwrap_or_else(|e| panic!("committed fixture {path} could not be read: {e}"));
let field = DataField::parse(&bytes).expect("fixture must parse");
println!("PES data field: {} bytes", bytes.len());
println!("data_identifier: 0x{:02X}", field.data_identifier);
println!("data units: {}", field.data_units.len());
for u in &field.data_units {
print!(
" {} (id 0x{:02X}), data_unit_length {}",
u.id,
u.id.to_u8(),
u.data_unit_length()
);
match &u.payload {
DataUnitPayload::Vps(f) => print!(" — line_offset {}", f.header.line_offset),
DataUnitPayload::Wss(f) => print!(" — wss 0x{:04X}", f.wss_data_block),
DataUnitPayload::ClosedCaptioning(f) => {
print!(" — cc 0x{:04X}", f.closed_captioning_data_block)
}
DataUnitPayload::Teletext(f) => print!(" — framing 0x{:02X}", f.framing_code),
DataUnitPayload::Monochrome(f) => print!(" — {} samples", f.samples.len()),
DataUnitPayload::Stuffing { length } => print!(" — {length} stuffing bytes"),
DataUnitPayload::Opaque(b) => print!(" — {} opaque bytes", b.len()),
_ => {}
}
println!();
}
// Byte-exact round-trip.
let mut out = vec![0u8; field.serialized_len()];
let n = field.serialize_into(&mut out).unwrap();
assert_eq!(n, bytes.len());
assert_eq!(
out, bytes,
"serialize must be byte-identical to the fixture"
);
assert_eq!(DataField::parse(&out).unwrap(), field);
println!("round-trip byte-exact: OK");
}Structs§
- Closed
Captioning Data Field - Closed Captioning data field — ETSI EN 301 775 §4.8.1, Table 10
(
data_unit_id0xC5). - Data
Field - The PES data field — ETSI EN 301 775 §4.4.1, Table 1.
- Data
Unit - One data unit: a
data_unit_id, itsdata_unit_length, and the typed body (ETSI EN 301 775 §4.4.1, Table 1 loop body). - Line
Header - The shared first byte of the Teletext / VPS / WSS / CC data fields:
a fixed
reserved_future_use=11prefix, thenfield_parityand a 5-bitline_offset(ETSI EN 301 775 §4.5.1 et al.). - Monochrome
Data Field - Monochrome 4:2:2 luminance-sample data field — ETSI EN 301 775 §4.9.1,
Table 12 (
data_unit_id0xC6). - Teletext
Data Field - EBU / Inverted Teletext data field — ETSI EN 301 775 §4.5.1, Table 4
(
data_unit_id0x02,0x03,0xC0). - VpsData
Field - VPS data field — ETSI EN 301 775 §4.6.1, Table 6 (
data_unit_id0xC3). - WssData
Field - WSS data field — ETSI EN 301 775 §4.7.1, Table 8 (
data_unit_id0xC4).
Enums§
- Data
Unit Id - A decoded
data_unit_id(ETSI EN 301 775 §4.4.2, Table 3). - Data
Unit Payload - The typed body of one data unit (ETSI EN 301 775 §4.4, dispatched on
data_unit_id). - Error
- A VBI parse / serialize error.
Constants§
- CC_
FIELD_ LEN - Size in bytes of a Closed Captioning data field (header + 16 CC bits = 3 bytes, §4.8).
- FRAMING_
CODE_ EBU - EBU Teletext framing_code (
11100100, §4.5.2). - FRAMING_
CODE_ INVERTED - Inverted Teletext framing_code (
00011011, §4.5.2). - ID_
CLOSED_ CAPTIONING data_unit_idvalue: Closed Captioning (0xC5, Table 3).- ID_
EBU_ TELETEXT_ NON_ SUBTITLE data_unit_idvalue: EBU Teletext non-subtitle data (0x02, Table 3).- ID_
EBU_ TELETEXT_ SUBTITLE data_unit_idvalue: EBU Teletext subtitle data (0x03, Table 3).- ID_
INVERTED_ TELETEXT data_unit_idvalue: Inverted Teletext (0xC0, Table 3).- ID_
MONOCHROME_ 422_ SAMPLES data_unit_idvalue: monochrome 4:2:2 samples (0xC6, Table 3).- ID_
STUFFING data_unit_idvalue: stuffing (0xFF, Table 3).- ID_VPS
data_unit_idvalue: VPS (0xC3, Table 3).- ID_WSS
data_unit_idvalue: WSS (0xC4, Table 3).- LINE_
HEADER_ LEN - Size in bytes of the shared first-byte line header.
- MONO_
HEADER_ LEN - Size in bytes of the monochrome fixed header preceding the
Y_valuesamples: first byte (flags + parity + line_offset) + 16-bit first_pixel_position + 8-bit n_pixels (§4.9.1). - RESERVED_
PREFIX - The fixed 2-bit
reserved_future_useprefix (11) occupying bits[7:6]of the header byte. - TELETEXT_
DATA_ UNIT_ LENGTH - The fixed
data_unit_lengthfordata_identifier0x10–0x1F(0x2C= 44, the Teletext data-field body length — §4.4.2). - TELETEXT_
FIELD_ LEN - Size in bytes of a Teletext data field (header + framing_code + block).
- TXT_
DATA_ BLOCK_ LEN - Size in bytes of the EBU/Inverted Teletext
txt_data_block(336 bits, §4.5). - VPS_
DATA_ BLOCK_ LEN - Size in bytes of the VPS
vps_data_block(104 bits, §4.6). - VPS_
FIELD_ LEN - Size in bytes of a VPS data field (header + block).
- WSS_
DATA_ BLOCK_ MASK - Mask for the 14-bit
wss_data_block(§4.7). - WSS_
FIELD_ LEN - Size in bytes of a WSS data field (header + 14 wss bits + 2-bit RFU = 3 bytes, §4.7).
- WSS_
RESERVED_ TAIL - The trailing 2-bit
reserved_future_use(11) of a WSS data field (§4.7.1).
Type Aliases§
- Result
- Result alias for VBI parsing.