Skip to main content

Crate dvb_vbi

Crate dvb_vbi 

Source
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 shared LineHeader + an 8-bit framing_code + a 42-byte opaque txt_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-bit wss_data_block + a 2-bit reserved_future_use 11 tail.
  • 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), a first_pixel_position, n_pixels, and the luminance Y_value bytes.
  • Stuffing (0xFF, §4.4.1) and an Opaque catch-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§

ClosedCaptioningDataField
Closed Captioning data field — ETSI EN 301 775 §4.8.1, Table 10 (data_unit_id 0xC5).
DataField
The PES data field — ETSI EN 301 775 §4.4.1, Table 1.
DataUnit
One data unit: a data_unit_id, its data_unit_length, and the typed body (ETSI EN 301 775 §4.4.1, Table 1 loop body).
LineHeader
The shared first byte of the Teletext / VPS / WSS / CC data fields: a fixed reserved_future_use = 11 prefix, then field_parity and a 5-bit line_offset (ETSI EN 301 775 §4.5.1 et al.).
MonochromeDataField
Monochrome 4:2:2 luminance-sample data field — ETSI EN 301 775 §4.9.1, Table 12 (data_unit_id 0xC6).
TeletextDataField
EBU / Inverted Teletext data field — ETSI EN 301 775 §4.5.1, Table 4 (data_unit_id 0x02, 0x03, 0xC0).
VpsDataField
VPS data field — ETSI EN 301 775 §4.6.1, Table 6 (data_unit_id 0xC3).
WssDataField
WSS data field — ETSI EN 301 775 §4.7.1, Table 8 (data_unit_id 0xC4).

Enums§

DataUnitId
A decoded data_unit_id (ETSI EN 301 775 §4.4.2, Table 3).
DataUnitPayload
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_id value: Closed Captioning (0xC5, Table 3).
ID_EBU_TELETEXT_NON_SUBTITLE
data_unit_id value: EBU Teletext non-subtitle data (0x02, Table 3).
ID_EBU_TELETEXT_SUBTITLE
data_unit_id value: EBU Teletext subtitle data (0x03, Table 3).
ID_INVERTED_TELETEXT
data_unit_id value: Inverted Teletext (0xC0, Table 3).
ID_MONOCHROME_422_SAMPLES
data_unit_id value: monochrome 4:2:2 samples (0xC6, Table 3).
ID_STUFFING
data_unit_id value: stuffing (0xFF, Table 3).
ID_VPS
data_unit_id value: VPS (0xC3, Table 3).
ID_WSS
data_unit_id value: 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_value samples: 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_use prefix (11) occupying bits [7:6] of the header byte.
TELETEXT_DATA_UNIT_LENGTH
The fixed data_unit_length for data_identifier 0x100x1F (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.