Skip to content

Repository files navigation

mpeg2ts-reader

Rust reader for MPEG2 Transport Stream data

crates.io versionDocumentationCoverage StatusUnstable 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]externcrate mpeg2ts_reader;externcrate 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 traitpacket_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 aboutNull: 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() methodimplDumpDemuxContext{fndo_construct(&mutself,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 stdoutpubstructPtsDumpElementaryStreamConsumer{pid: packet::Pid,len:Option<usize>,}implPtsDumpElementaryStreamConsumer{fnconstruct(_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::ElementaryStreamConsumerforPtsDumpElementaryStreamConsumer{fnstart_stream(&mutself){}fnbegin_packet(&mutself,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))}}}fncontinue_packet(&mutself,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());}fnend_packet(&mutself){println!("{:?}: end of packet length={:?}",self.pid,self.len);}fncontinuity_error(&mutself){}}fnmain(){// open input file named on command line,let name = env::args().nth(1).unwrap();letmut f = File::open(&name).expect(&format!("file not found: {}",&name));// create the context object that stores the state of the transport stream demultiplexing// processletmut ctx = DumpDemuxContext::new();// create the demultiplexer, which will use the ctx to create a filter for pid 0 (PAT)letmut demux = demultiplex::Demultiplex::new(&mut ctx);// consume the input file,letmut 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
    • 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
    • Fixed headers
    • Adaptation field
    • TS-level scrambling (values of transport_scrambling_control other than 0) not supported
  • Program Specific Information tables
    • Section syntax
    • 'Multi-section' tables
    • PAT - Program Association Table
    • PMT - Program Mapping Table
    • TSDT - Transport Stream Description Table
  • Packetised Elementary Stream syntax
    • PES_packet_data
    • PTS/DTS
    • ESCR
    • ES_rate
    • DSM_trick_mode
    • additional_copy_info
    • PES_CRC
    • PES_extension
  • Descriptors
    • video_stream_descriptor
    • audio_stream_descriptor
    • hierarchy_descriptor
    • registration_descriptor
    • data_stream_alignment_descriptor
    • target_background_grid_descriptor
    • video_window_descriptor
    • ca_descriptor
    • iso_639_language_descriptor
    • system_clock_descriptor
    • multiplex_buffer_utilization_descriptor
    • copyright_descriptor
    • 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
    • AVC_video_descriptor

Related crates

Since this crate only covers the core Transport Stream spec, related specs are to be covered by other crates.

  • scte35-reader extends mpeg2ts-reader to support SCTE-35 data.
  • en-300-468-reader extends mpeg2ts-reader to support reading the Service Description Table (SDT) defined in ETSI standard EN 300 486.

About

Rust reader for MPEG2 Transport Stream data

Topics

Resources

Stars

63 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages