Skip to main content

Crate cc_data

Crate cc_data 

Source
Expand description

Closed-caption cc_data() carriage (CEA-608/708), per ETSI TS 101 154 §B.5, Table B.9.

Parses the closed-caption carriage structure carried in MPEG-2 / AVC / HEVC picture user_data (the DVB-native, normative form of the ATSC/CEA cc_data()). Exposes the typed caption triplets (cc_valid, cc_type, cc_data_1/2) and a CEA-608 vs CEA-708 split by cc_type.

Feed it the cc_data() bytes (the caller extracts them from the picture user_data / SEI). Depends only on dvb-common, #![no_std] (+ alloc).

§Caption decode (decode feature)

With the default decode feature, this crate also interprets the demuxed caption byte pairs — the decode module’s Cea608Decoder (line-21 pop-on / roll-up / paint-on, PACs, mid-row codes, the standard / special / extended character sets, channels CC1–CC4) and Cea708Decoder (DTVCC packet / service-block reassembly + the C0/C1/G0/G1/G2/G3 window / pen command interpreter, services 1–6) — and exposes the decoded caption screen / window text. Grounded in ANSI/CTA-608-E, ANSI/CTA-708-E and 47 CFR §79.102 (see cc-data/docs/decode/).

§Examples

Runnable examples ship with this crate (cargo run -p cc-data --example <name>).

§parse_cc_data

//! Basic: parse a `cc_data()` byte sequence and split CEA-608 / CEA-708 triplets.
//!
//! Run with: `cargo run -p cc-data --example parse_cc_data`

use cc_data::CcData;
use dvb_common::Parse;

fn main() {
    // cc_data: process_cc_data_flag=1, cc_count=2; one DTVCC-start triplet + one
    // 608-field-1 triplet; trailing 0xFF marker.
    #[rustfmt::skip]
    let bytes = [
        0b1100_0010, // reserved=1, process=1, zero=0, cc_count=2
        0xFF,        // reserved
        0xFF, 0xC1, 0x02, // one_bit+rsvd(F8) | valid=1 | type=3 (708 start); data C1 02
        0xFC, 0x94, 0x2C, // F8 | valid=1 | type=0 (608 field1); data 94 2C
        0xFF,        // marker
    ];

    let cc = CcData::parse(&bytes).expect("valid cc_data");
    println!("process_cc_data_flag : {}", cc.process_cc_data_flag);
    println!("triplets             : {}", cc.triplets.len());
    for t in &cc.triplets {
        println!(
            "  {:<16} valid={} data={:02X} {:02X}",
            t.cc_type.name(),
            t.cc_valid,
            t.cc_data_1,
            t.cc_data_2
        );
    }
    println!("CEA-608 triplets     : {}", cc.cea608().count());
    println!("CEA-708 triplets     : {}", cc.cea708().count());
}

§build_cc_data

//! Advanced: build a `cc_data()` from typed triplets, serialize, and round-trip.
//!
//! Run with: `cargo run -p cc-data --example build_cc_data`

use cc_data::{CcData, CcTriplet, CcType};
use dvb_common::{Parse, Serialize};

fn main() {
    let cc = CcData {
        process_cc_data_flag: true,
        triplets: vec![
            CcTriplet {
                cc_valid: true,
                cc_type: CcType::Dtvcc708Start,
                cc_data_1: 0xC1,
                cc_data_2: 0x02,
            },
            CcTriplet {
                cc_valid: true,
                cc_type: CcType::Ntsc608Field1,
                cc_data_1: 0x94,
                cc_data_2: 0x2C,
            },
        ],
    };

    let bytes = cc.to_bytes();
    println!("serialized {} bytes: {:02X?}", bytes.len(), bytes);

    let back = CcData::parse(&bytes).expect("round-trip parse");
    assert_eq!(back, cc, "round-trip must be lossless");
    println!(
        "round-trip OK — {} triplets ({} 608, {} 708)",
        back.triplets.len(),
        back.cea608().count(),
        back.cea708().count()
    );
}

§decode_cea608

//! Decode a CEA-608 (line-21) pop-on caption to on-screen text.
//!
//! Run with: `cargo run -p cc-data --example decode_cea608`

use cc_data::decode::{Cea608Channel, Cea608Decoder};

/// Add odd parity to a 7-bit value so the pairs look like real line-21 bytes.
fn par(v: u8) -> u8 {
    if (v & 0x7F).count_ones() % 2 == 0 {
        v | 0x80
    } else {
        v & 0x7F
    }
}

fn main() {
    let mut dec = Cea608Decoder::new();

    // A pop-on caption on CC1 (field 1):
    //   RCL  (14 20)  — Resume Caption Loading (pop-on)
    //   PAC  (14 70)  — row 15, white, indent 0
    //   "HELLO"
    //   EOC  (14 2F)  — flip back buffer to the screen
    let pairs: &[(u8, u8)] = &[
        (0x14, 0x20),
        (0x14, 0x70),
        (b'H', b'E'),
        (b'L', b'L'),
        (b'O', 0x00),
        (0x14, 0x2F),
    ];
    for &(b1, b2) in pairs {
        dec.push_pair(false, par(b1), par(b2));
    }

    println!("CC1 mode : {}", dec.mode(Cea608Channel::Cc1));
    println!("CC1 text : {:?}", dec.channel_text(Cea608Channel::Cc1));

    // A roll-up caption (2 rows) on CC1.
    let mut dec2 = Cea608Decoder::new();
    let roll: &[(u8, u8)] = &[
        (0x14, 0x25), // RU2
        (b'O', b'N'),
        (b'E', 0x00),
        (0x14, 0x2D), // CR
        (b'T', b'W'),
        (b'O', 0x00),
    ];
    for &(b1, b2) in roll {
        dec2.push_pair(false, par(b1), par(b2));
    }
    println!("\nRoll-up mode : {}", dec2.mode(Cea608Channel::Cc1));
    println!("Roll-up text :\n{}", dec2.channel_text(Cea608Channel::Cc1));
}

§decode_cea708

//! Decode a CEA-708 (DTVCC) caption packet to window text.
//!
//! Run with: `cargo run -p cc-data --example decode_cea708`

use cc_data::decode::Cea708Decoder;

fn main() {
    let mut dec = Cea708Decoder::new();

    // Build a Caption Channel Packet carrying one Service Block for service 1
    // that: DefineWindow DF0 (visible, 2 rows × 16 cols), writes "HI THERE".
    let block: &[u8] = &[
        0x98, // DefineWindow DF0
        0x20, // parm1: visible=YES, priority 0
        0x00, // parm2: rp=0, anchor vertical 0
        0x00, // parm3: anchor horizontal 0
        0x01, // parm4: anchor point 0, row count = 1+1 = 2
        0x0F, // parm5: column count = 15+1 = 16
        0x00, // parm6: window/pen style 0 (auto)
        b'H', b'I', b' ', b'T', b'H', b'E', b'R', b'E',
    ];

    // Service Block header: service_number = 1, block_size = block.len().
    let mut sb: Vec<u8> = Vec::new();
    sb.push((1 << 5) | (block.len() as u8));
    sb.extend_from_slice(block);

    // CCP header: sequence_number 0, packet_size_code covers the data.
    let size_code = (sb.len().div_ceil(2) + 1) as u8;
    let mut ccp: Vec<u8> = Vec::new();
    ccp.push(size_code & 0x3F);
    ccp.extend_from_slice(&sb);

    dec.push_packet(&ccp);

    println!("Service 1 text : {:?}", dec.service_text(1));
    if let Some(w) = dec.windows(1)[0].as_ref() {
        println!("Window 0 state : {}", w.state);
        println!("Window 0 rows  : {}", w.row_count);
        println!("Window 0 cols  : {}", w.column_count);
        println!("Window 0 text  : {:?}", w.text());
    }
}

Modules§

decodedecode
CEA-608 / CEA-708 caption decode — the meaning of the caption byte pairs that the crate::CcData carriage demuxes out of cc_data().

Structs§

CcData
cc_data() — the DVB closed-caption carriage structure (Table B.9).
CcTriplet
One closed-caption construct (the per-cc_count loop entry of Table B.9).

Enums§

CcType
cc_type — the type of the caption data byte pair (TS 101 154 Table B.9 / CEA-708-E). 2-bit field.
Error
A cc_data parse error.

Type Aliases§

Result
Result alias.