go-astits

repository·master·Indexed 20 days ago

https://github.com/asticode/go-astits

A native Go library for demultiplexing (demuxing) and multiplexing (muxing) MPEG Transport Streams (TS). It provides a programmatic API for Go applications and CLI tools, including astits-probe for inspecting .ts files and UDP multicast streams, and astits-es-split for splitting elementary streams into separate files. The library includes support for PAT, PMT, PES, EIT, and NIT data structures, as well as ClockReference for timing and synchronization.

Tokens
17.4K
Snippets
68
Records
79
Agent score
69%

What's inside go-astits

  1. Install the go-astits library and CLI tools

    master

    To use the library in your Go projects, install it using go get. To install the provided command-line executables (astits-probe and astits-es-split), install the cmd package.

    Note: The library is not yet production ready. Use at your own risk.

    # Install the library
    go get -u github.com/asticode/go-astits/...
    
    # Install the CLI executables
    go install github.com/asticode/go-astits/cmd
  2. Understand the PSI Data structure

    master

    The PSIData struct represents the parsed Program-Specific Information. It consists of a PointerField (used for packet alignment) and a slice of PSISection pointers.

    Each PSISection contains:

    • CRC32: A checksum of the table (excluding the pointer field, filler, and the CRC itself).
    • Header: A PSISectionHeader containing metadata like TableID and SectionLength.
    • Syntax: A PSISectionSyntax containing the actual table data (e.g., PAT, PMT, NIT, etc.) and its syntax header.
    type PSIData struct {
    	PointerField int // Present at the start of the TS packet payload
    	Sections     []*PSISection
    }
    
    type PSISection struct {
    	CRC32  uint32
    	Header *PSISectionHeader
    	Syntax *PSISectionSyntax
    }
  3. Implement a PacketSkipper to filter packets

    master

    A PacketSkipper is a function type used to filter out unwanted packets from the pipeline before they are processed. The function receives the parsed header and adaptation field of the packet.

    Signature: type PacketSkipper func(p *Packet) (skip bool)

    If the function returns true, the packet is skipped, and NextPacket() will automatically attempt to return the next unskipped packet.

    skipper := func(p *astits.Packet) bool {
    	// Example: skip packets with a specific PID if needed
    	return p.Pid == 0x123
    }
    
    dmx, _ := astits.NewDemuxer(ctx, r, astits.DemuxerOptPacketSkipper(skipper))
  4. Packet structure in astits

    master

    The Packet type represents a single MPEG transport stream packet. It consists of a header, an optional adaptation field, and the payload content.

    Key components:

    • Header: Contains metadata like PID, continuity counter, and scrambling control.
    • AdaptationField: An optional field containing clock references (PCR/OPCR), splicing information, or stuffing bytes.
    • Payload: The actual data content of the packet (excluding the header and adaptation field).
    type Packet struct {
    	AdaptationField *PacketAdaptationField
    	Header          PacketHeader
    	Payload         []byte // This is only the payload content
    }
  5. Implement a PacketsParser for custom payload parsing

    master

    A PacketsParser allows you to define how a sequence of packets is converted into DemuxerData. This is useful if you need to handle specific payload types or custom parsing logic.

    Signature: type PacketsParser func(ps []*Packet) (ds []*DemuxerData, skip bool, err error)

    • ps: A slice of packets containing a unique payload.
    • ds: The resulting slice of DemuxerData objects.
    • skip: If true, indicates the default processing should be skipped.
    • err: Any error encountered during parsing.
    parser := func(ps []*astits.Packet) ([]*astits.DemuxerData, bool, error) {
    	// custom parsing logic here
    	return ds, false, nil
    }
    
    dmx, _ := astits.NewDemuxer(ctx, r, astits.DemuxerOptPacketsParser(parser))
  6. astits-probe command modes

    master

    The astits-probe tool executes different logic based on the first positional argument provided:

    • data: Iterates through the stream using dmx.NextData() and logs specific MPEG data structures. You can filter which types are logged using the -d flag.
    • packets: Iterates through the stream using dmx.NextPacket() and logs detailed header information for every packet (PID, Continuity Counter, etc.).
    • default (or any other string): Scans the stream to build a list of Program objects, including their Map IDs, descriptors, and elementary streams. Output can be formatted as json using the -f flag.
  7. Use the Descriptor struct to access MPEG descriptor data

    master

    The Descriptor struct is the primary container for parsed MPEG descriptor data. It contains a Tag field indicating the descriptor type and several optional pointers to specific descriptor types (e.g., AC3, AVCVideo, Service). When a descriptor is parsed, the pointer corresponding to its Tag will be non-nil.

    // Example conceptual usage
    if desc.Tag == astits.DescriptorTagService {
        // Access service-specific data
        if desc.Service != nil {
            fmt.Printf("Service Name: %s\n", string(desc.Service.Name))
        }
    }
  8. Use the astits-probe CLI to inspect MPEG transport streams

    master

    astits-probe is a command-line tool used to probe MPEG transport streams. It can read from local files or multicast UDP streams. The tool provides three primary modes of operation:

    1. data: Fetches and logs specific MPEG data types (PAT, PMT, PES, etc.).
    2. packets: Fetches and logs individual transport stream packets.
    3. default (or any other command): Fetches and lists the programs found in the stream.

    Usage

    astits-probe <data|packets|default> [flags]

    Input Schemes

    • File: Use the path directly (e.g., -i /path/to/stream.ts).
    • UDP Multicast: Use the udp:// scheme (e.g., -i udp://239.0.0.1:1234).
    # List programs in a local file
    astits-probe default -i ./stream.ts
    
    # List programs in JSON format from a UDP multicast stream
    astits-probe default -i udp://239.0.0.1:1234 -f json
    
    # Fetch specific data types (e.g., PAT and PMT only)
    astits-probe data -i ./stream.ts -d pat pmt
    
    # List all packets
    astits-probe packets -i ./stream.ts
  9. Demux MPEG Transport Streams

    master

    To demultiplex (demux) a transport stream, use astits.NewDemuxer. It requires a context.Context for cancellation and an io.Reader (using bufio.Reader is recommended for performance). You can iterate through the stream using dmx.NextData() to retrieve parsed data segments, such as PMT (Program Map Table) data.

    // Create a cancellable context
    ctx, cancel := context.WithCancel(context.Background())
    
    // Open your file or initialize any kind of io.Reader
    f, _ := os.Open("/path/to/file.ts")
    defer f.Close()
    
    // Create the demuxer
    dmx := astits.NewDemuxer(ctx, f)
    for {
        // Get the next data
        d, _ := dmx.NextData()
        
        // Example: Handle PMT data
        if d.PMT != nil {
            for _, es := range d.PMT.ElementaryStreams {
                fmt.Printf("Stream detected: %d\n", es.ElementaryPID)
            }
            return
        }
    }
  10. Mux MPEG Transport Streams

    master

    To multiplex (mux) data into a transport stream, use astits.NewMuxer. It requires a context.Context and an io.Writer (using bufio.Writer is recommended).

    Workflow:

    1. Create the muxer.
    2. Add elementary streams using mx.AddElementaryStream.
    3. Write tables using mx.WriteTables() (optional, as WriteData handles periodic retransmission).
    4. Write data using mx.WriteData by providing a *astits.MuxerData object containing the PID and payload (e.g., PESData).
    // Create a cancellable context
    ctx, cancel := context.WithCancel(context.Background())
    
    // Create your file or initialize any kind of io.Writer
    f, _ := os.Create("/path/to/file.ts")
    defer f.Close()
    
    // Create the muxer
    mx := astits.NewMuxer(ctx, f)
    
    // Add an elementary stream
    mx.AddElementaryStream(astits.PMTElementaryStream{
        ElementaryPID: 1,
        StreamType:    astits.StreamTypeMetadata,
    })
    
    // Write tables
    mx.WriteTables()
    
    // Write data
    mx.WriteData(&astits.MuxerData{
        PES: &astits.PESData{
            Data: []byte("test"),
        },
        PID: 1,
    })
  11. Configure Demuxer and Muxer with Options

    master

    You can customize the behavior of the demuxer or muxer by passing functional options to NewDemuxer or NewMuxer. Look for methods prefixed with DemuxerOpt or MuxerOpt.

    Example: Providing a custom packet parser using DemuxerOptPacketsParser.

    // This is your custom packets parser
    p := func(ps []*astits.Packet) (ds []*astits.Data, skip bool, err error) {
        // Your logic here
        skip = true
        return
    }
    
    // Create a demuxer with custom options
    dmx := astits.NewDemuxer(ctx, f, astits.DemuxerOptPacketSize(192), astits.DemuxerOptPacketsParser(p))
  12. Configure Demuxer options

    master

    The Demuxer can be customized using several functional options passed to NewDemuxer:

    • DemuxerOptLogger(l astikit.StdLogger): Sets a custom logger.
    • DemuxerOptPacketSize(packetSize int): Sets the expected size of a single packet (e.g., 188).
    • DemuxerOptPacketsParser(p PacketsParser): Provides a custom function to parse sets of packets into DemuxerData.
    • DemuxerOptPacketSkipper(s PacketSkipper): Provides a function to filter out specific packets before they are parsed.
    opts := []func(*astits.Demuxer){
    	astits.DemuxerOptPacketSize(188),
    	astits.DemuxerOptPacketSkipper(func(p *astits.Packet) bool {
    		// return true to skip the packet
    		return false
    	}),
    }
    dmx, err := astits.NewDemuxer(ctx, reader, opts...)