MCPcopy Index your code
hub / github.com/dholroyd/mpeg2ts-reader

github.com/dholroyd/mpeg2ts-reader @v0.18.2

Chat with this repo
repository ↗ · DeepWiki ↗ · release v0.18.2 ↗ · + Follow
433 symbols 898 edges 27 files 133 documented · 31%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

mpeg2ts-reader

Rust reader for MPEG2 Transport Stream data

crates.io version Documentation Coverage Status Unstable API

Zero-copy access to payload data within an MPEG Transport Stream.

This crate, - implements a low-level state machine that recognises the structural elements of Transport Stream syntax - provides traits that you should implement to define your application-specific processing of the contained data.

Example

Dump H264 payload data as hex.

#[macro_use]
extern crate mpeg2ts_reader;
extern crate hex_slice;

use hex_slice::AsHex;
use mpeg2ts_reader::demultiplex;
use mpeg2ts_reader::packet;
use mpeg2ts_reader::pes;
use mpeg2ts_reader::psi;
use mpeg2ts_reader::StreamType;
use std::cmp;
use std::env;
use std::fs::File;
use std::io::Read;

// This macro invocation creates an enum called DumpFilterSwitch, encapsulating all possible ways
// that this application may handle transport stream packets.  Each enum variant is just a wrapper
// around an implementation of the PacketFilter trait
packet_filter_switch! {
    DumpFilterSwitch<DumpDemuxContext> {
        // the DumpFilterSwitch::Pes variant will perform the logic actually specific to this
        // application,
        Pes: pes::PesPacketFilter<DumpDemuxContext,PtsDumpElementaryStreamConsumer>,

        // these definitions are boilerplate required by the framework,
        Pat: demultiplex::PatPacketFilter<DumpDemuxContext>,
        Pmt: demultiplex::PmtPacketFilter<DumpDemuxContext>,

        // this variant will be used when we want to ignore data in the transport stream that this
        // application does not care about
        Null: demultiplex::NullPacketFilter<DumpDemuxContext>,
    }
}

// This macro invocation creates a type called DumpDemuxContext, which is our application-specific
// implementation of the DemuxContext trait.
demux_context!(DumpDemuxContext, DumpFilterSwitch);

// When the de-multiplexing process needs to create a PacketFilter instance to handle a particular
// kind of data discovered within the Transport Stream being processed, it will send a
// FilterRequest to our application-specific implementation of the do_construct() method
impl DumpDemuxContext {
    fn do_construct(&mut self, req: demultiplex::FilterRequest<'_, '_>) -> DumpFilterSwitch {
        match req {
            // The 'Program Association Table' is is always on PID 0.  We just use the standard
            // handling here, but an application could insert its own logic if required,
            demultiplex::FilterRequest::ByPid(packet::Pid::PAT) => {
                DumpFilterSwitch::Pat(demultiplex::PatPacketFilter::default())
            }
            // 'Stuffing' data on PID 0x1fff may be used to pad-out parts of the transport stream
            // so that it has constant overall bitrate.  This causes it to be ignored if present.
            demultiplex::FilterRequest::ByPid(packet::Pid::STUFFING) => {
                DumpFilterSwitch::Null(demultiplex::NullPacketFilter::default())
            }
            // Some Transport Streams will contain data on 'well known' PIDs, which are not
            // announced in PAT / PMT metadata.  This application does not process any of these
            // well known PIDs, so we register NullPacketFiltet such that they will be ignored
            demultiplex::FilterRequest::ByPid(_) => {
                DumpFilterSwitch::Null(demultiplex::NullPacketFilter::default())
            }
            // This match-arm installs our application-specific handling for each H264 stream
            // discovered within the transport stream,
            demultiplex::FilterRequest::ByStream {
                stream_type: StreamType::H264,
                pmt,
                stream_info,
                ..
            } => PtsDumpElementaryStreamConsumer::construct(pmt, stream_info),
            // We need to have a match-arm to specify how to handle any other StreamType values
            // that might be present; we answer with NullPacketFilter so that anything other than
            // H264 (handled above) is ignored,
            demultiplex::FilterRequest::ByStream { .. } => {
                DumpFilterSwitch::Null(demultiplex::NullPacketFilter::default())
            }
            // The 'Program Map Table' defines the sub-streams for a particular program within the
            // Transport Stream (it is common for Transport Streams to contain only one program).
            // We just use the standard handling here, but an application could insert its own
            // logic if required,
            demultiplex::FilterRequest::Pmt {
                pid,
                program_number,
            } => DumpFilterSwitch::Pmt(demultiplex::PmtPacketFilter::new(pid, program_number)),
            // Ignore 'Network Information Table', if present,
            demultiplex::FilterRequest::Nit { .. } => {
                DumpFilterSwitch::Null(demultiplex::NullPacketFilter::default())
            }
        }
    }
}

// Implement the ElementaryStreamConsumer to just dump and PTS/DTS timestamps to stdout
pub struct PtsDumpElementaryStreamConsumer {
    pid: packet::Pid,
    len: Option<usize>,
}
impl PtsDumpElementaryStreamConsumer {
    fn construct(
        _pmt_sect: &psi::pmt::PmtSection,
        stream_info: &psi::pmt::StreamInfo,
    ) -> DumpFilterSwitch {
        let filter = pes::PesPacketFilter::new(PtsDumpElementaryStreamConsumer {
            pid: stream_info.elementary_pid(),
            len: None,
        });
        DumpFilterSwitch::Pes(filter)
    }
}
impl pes::ElementaryStreamConsumer for PtsDumpElementaryStreamConsumer {
    fn start_stream(&mut self) {}
    fn begin_packet(&mut self, header: pes::PesHeader) {
        match header.contents() {
            pes::PesContents::Parsed(Some(parsed)) => {
                match parsed.pts_dts() {
                    Ok(pes::PtsDts::PtsOnly(Ok(pts))) => {
                        print!("{:?}: pts {:#08x}                ", self.pid, pts.value())
                    }
                    Ok(pes::PtsDts::Both {
                        pts: Ok(pts),
                        dts: Ok(dts),
                    }) => print!(
                        "{:?}: pts {:#08x} dts {:#08x} ",
                        self.pid,
                        pts.value(),
                        dts.value()
                    ),
                    _ => (),
                }
                let payload = parsed.payload();
                self.len = Some(payload.len());
                println!(
                    "{:02x}",
                    payload[..cmp::min(payload.len(), 16)].plain_hex(false)
                )
            }
            pes::PesContents::Parsed(None) => (),
            pes::PesContents::Payload(payload) => {
                self.len = Some(payload.len());
                println!(
                    "{:?}:                               {:02x}",
                    self.pid,
                    payload[..cmp::min(payload.len(), 16)].plain_hex(false)
                )
            }
        }
    }
    fn continue_packet(&mut self, data: &[u8]) {
        println!(
            "{:?}:                     continues {:02x}",
            self.pid,
            data[..cmp::min(data.len(), 16)].plain_hex(false)
        );
        self.len = self.len.map(|l| l + data.len());
    }
    fn end_packet(&mut self) {
        println!("{:?}: end of packet length={:?}", self.pid, self.len);
    }
    fn continuity_error(&mut self) {}
}

fn main() {
    // open input file named on command line,
    let name = env::args().nth(1).unwrap();
    let mut f = File::open(&name).expect(&format!("file not found: {}", &name));

    // create the context object that stores the state of the transport stream demultiplexing
    // process
    let mut ctx = DumpDemuxContext::new();

    // create the demultiplexer, which will use the ctx to create a filter for pid 0 (PAT)
    let mut demux = demultiplex::Demultiplex::new(&mut ctx);

    // consume the input file,
    let mut buf = [0u8; 188 * 1024];
    loop {
        match f.read(&mut buf[..]).expect("read failed") {
            0 => break,
            n => demux.push(&mut ctx, &buf[0..n]),
        }
    }
}

Performance shoot-out

Comparing this crate to a couple of others which you might use to read a Transport Stream -- mpeg2ts and ffmpg-sys:

Performance

The benchmarks producing the above chart data are in the shootout folder. (If the benchmarks are giving an unfair representation of relative performance, that's a mistake -- please raise a bug!)

The conditions of the test are, * the data is already in memory (no network/disk access) * test dataset is larger than CPU cache * processing is happening on a single core (no multiprocessing of the stream).

Supported Transport Stream features

Not all Transport Stream features are supported yet. Here's a summary of what's available, and what's yet to come:

  • Framing
  • [x] ISO/IEC 13818-1 188-byte packets
  • [ ] m2ts 192-byte packets (would be nice if an external crate could support, at least)
  • [ ] recovery after loss of synchronisation
  • Transport Stream packet
  • [x] Fixed headers
  • [x] Adaptation field
  • [ ] TS-level scrambling (values of transport_scrambling_control other than 0) not supported
  • Program Specific Information tables
  • [x] Section syntax
  • [ ] 'Multi-section' tables
  • [x] PAT - Program Association Table
  • [x] PMT - Program Mapping Table
  • [ ] TSDT - Transport Stream Description Table
  • Packetised Elementary Stream syntax
  • [x] PES_packet_data
  • [x] PTS/DTS
  • [x] ESCR
  • [x] ES_rate
  • [x] DSM_trick_mode
  • [x] additional_copy_info
  • [x] PES_CRC
  • [ ] PES_extension
  • Descriptors
  • [ ] video_stream_descriptor
  • [ ] audio_stream_descriptor
  • [ ] hierarchy_descriptor
  • [x] registration_descriptor
  • [ ] data_stream_alignment_descriptor
  • [ ] target_background_grid_descriptor
  • [ ] video_window_descriptor
  • [ ] ca_descriptor
  • [x] iso_639_language_descriptor
  • [ ] system_clock_descriptor
  • [ ] multiplex_buffer_utilization_descriptor
  • [ ] copyright_descriptor
  • [x] maximum_bitrate_descriptor
  • [ ] private_data_indicator_descriptor
  • [ ] smoothing_buffer_descriptor
  • [ ] std_descriptor
  • [ ] ibp_descriptor
  • [ ] mpeg4_video_descriptor
  • [ ] mpeg4_audio_descriptor
  • [ ] iod_descriptor
  • [ ] sl_descriptor
  • [ ] fmc_descriptor
  • [ ] external_es_id_descriptor
  • [ ] muxcode_descriptor
  • [ ] fmxbuffersize_descriptor
  • [ ] multiplexbuffer_descriptor
  • [x] AVC_video_descriptor

Extension points exported contracts — how you extend this code

ElementaryStreamConsumer (Interface)
Trait for types that will receive call-backs as pieces of a specific elementary stream are encounted within a transport [6 …
src/pes.rs
PacketFilter (Interface)
Trait to which `Demultiplex` delegates handling of subsets of Transport Stream packets. A packet filter can collaborate [11 …
src/demultiplex.rs
WholeSectionSyntaxPayloadParser (Interface)
Trait for types that parse fully reconstructed PSI table sections (which requires the caller to have buffered section da [4 …
src/psi/mod.rs
Descriptor (Interface)
Trait allowing users of this trait to supply their own implementation of descriptor parsing. The default implementation
src/descriptor/mod.rs
DemuxContext (Interface)
Context shared between the `Dumultiplex` object and the `PacketFilter` instances which customise its behavior. This tra [1 …
src/demultiplex.rs
SectionSyntaxPayloadParser (Interface)
Trait for types that will handle MPEGTS PSI table sections with 'section syntax'. [4 implementers]
src/psi/mod.rs
SectionProcessor (Interface)
Trait for types which process the data within a PSI section following the 12-byte `section_length` field (which is one o [3 …
src/psi/mod.rs
CompactSyntaxPayloadParser (Interface)
Trait for types that will handle MPEGTS PSI table sections with 'compact syntax'. [2 implementers]
src/psi/mod.rs

Core symbols most depended-on inside this repo

push
called by 17
src/demultiplex.rs
insert
called by 12
src/demultiplex.rs
slice
called by 11
src/packet.rs
make_test_data
called by 9
src/pes.rs
header_slice
called by 8
src/pes.rs
is_empty
called by 8
src/demultiplex.rs
start_section
called by 8
src/psi/mod.rs
contents
called by 7
src/pes.rs

Shape

Method 247
Function 83
Class 72
Enum 22
Interface 9

Languages

Rust100%
Python1%

Modules by API surface

src/pes.rs100 symbols
src/packet.rs74 symbols
src/psi/mod.rs49 symbols
src/demultiplex.rs42 symbols
src/psi/pmt.rs22 symbols
src/descriptor/avcvideo.rs17 symbols
src/descriptor/iso_639_language.rs16 symbols
shootout/src/mpeg2ts_reader_timestamps.rs11 symbols
src/descriptor/mod.rs10 symbols
examples/strip.rs10 symbols
fuzz/fuzz_targets/fuzz_target_1.rs9 symbols
examples/simple.rs9 symbols

For agents

$ claude mcp add mpeg2ts-reader \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact