Skip to content

Documentation

The TSDuck Cookbook

A hands-on TSDuck guide with every command tested on version 3.44 and shown with real output. It covers the tsp pipeline, analysing live SAT>IP streams, reading and editing PSI/SI as XML, PCR and continuity checks, conditional access, T2-MI, IP outputs and failover, plus the traps found while testing.

  • 2026-10-09 14:40
  • 2026-10-13 13:05

The Model

One input, N processors, one output

Learn this shape and every tsp command reads the same way.

1. Scope, version, and how these recipes were verified

Every command on this page was executed. Nothing is transcribed from documentation and nothing is reconstructed from memory. The environment was TSDuck 3.44-4676 — the current release at the time of writing, published in May 2026 — installed from Homebrew on macOS. The same commands behave identically on Linux; only the installation step differs.

Why this matters more than usual for a cookbook. TSDuck has a large option surface and several plugins accept option names that look interchangeable but are not. While building this page, several recipes written from reasonable assumptions failed outright, one silently destroyed its own input file, and two more are traps in the SAT>IP query rather than in TSDuck itself. All of them are documented in section 20 rather than quietly corrected, because the failure modes are more instructive than the working commands.

The test material is a synthetic multi-programme transport stream built with TSDuck itself — no captured broadcast content, no operator data. Section 9 gives the full recipe for building it, which makes every other recipe on this page reproducible from nothing but a TSDuck install.

2. The one idea that makes TSDuck click

TSDuck ships 43 separate commands, which makes it look sprawling. It is not. One command — tsp, the transport stream processor — does almost everything, and it has exactly one shape: an input plugin, a chain of packet processing plugins, and an output plugin.

Packets flow left to right. Each -P stage sees the stream as the previous stage left it. That is the whole model. The other 42 commands are either convenience wrappers around a common pipeline (tsanalyze is close to tsp -P analyze -O drop) or standalone utilities that do not process a stream at all (tstabcomp compiles table XML; tscrc32 computes a checksum).

Two consequences worth internalising early:

The plugin inventory in 3.44 breaks down as follows, and knowing the shape of it saves a lot of searching.

The four SimulCrypt commands are worth noticing. tsecmg and tsemmg are working ECMG and EMMG simulators, and tstestecmg is a conformance tester for a vendor’s ECMG. If you are integrating a CA system against the interfaces described in the conditional access sheet, these let you exercise the head-end side before the vendor equipment arrives.

3. Installing

TSDuck is packaged for most platforms and the 3.44 release added binaries for s390x, RISC-V 64-bit and PowerPC alongside the usual targets.

On this system that reports 3.44-4676. Check it before following any cookbook, including this one — option names do change between major versions, and a recipe that assumes the wrong version fails in ways that look like your stream is broken rather than your command.

Hardware tuner support is a build-time decision. The dvb input plugin, and the tslsdvb and tsscan commands, only do anything on a build with DVB device support and a tuner present. On a Homebrew macOS install they exist but have nothing to talk to. This is worth knowing before spending time debugging why tslsdvb lists no devices.

4. First look at an unknown stream

One command answers most of the questions you have about a file you have just been handed.

The real output on the demo stream, abbreviated to the header:

The per-service breakdown follows, and this is what you show someone who asks “what is in this multiplex”:

Two shortcuts are worth committing to memory, because they compose into shell pipelines where the full report does not:

Note that --service-list gives decimal service IDs while the report shows hex. 257 is 0x0101. And 8191 in the PID list is 0x1FFF, the null PID — stuffing, always present in a constant-bitrate stream.

For bitrate alone there is a dedicated command, which is faster than parsing the report:

The two figures are the same stream measured with and without the 16 bytes of Reed-Solomon parity that a satellite carrier adds. Quote the 204-byte number when you are sizing a transponder and the 188-byte number when you are sizing an IP link.

5. Analysing a live stream over HTTP

Everything above operates on a file, which is the easy case. In practice the stream you need to analyse is live, arriving from a SAT>IP server over HTTP, and the first thing you want is a service inventory. This is the single most-used command in day-to-day operation.

Quote the URL. This is the most common mistake by a wide margin, and on bash it fails silently and wrongly.

A SAT>IP URL is full of & characters, and & is the shell’s background-job operator. Unquoted, the shell tears the URL apart at the first ampersand. Verified behaviour:

# bash — silently wrong
$ tsp -I http http://sat-ip.example:8875/?freq=12606&sr=35300&pol=V&msys=dvbs2&pids=all

argv[3] = [http://sat-ip.example:8875/?freq=12606] ← everything after & is gone
+ sr=35300 ← became a shell variable
+ pol=V ← became a shell variable
...

Two things go wrong at once. The tuning parameters vanish, so the server receives a request with a frequency and nothing else — no symbol rate, no polarisation, no modulation — and cannot tune. And because the first & backgrounds the command, your prompt returns immediately, so it looks as though tsp exited cleanly. The symptom presents as a tuning or signal fault when the problem never left your shell.

zsh behaves differently, and better: it refuses with parse error near '&', and a bare ? in the URL also triggers no matches found because zsh globs it. So the same unquoted command is a loud error on zsh and a silent wrong answer on bash — which is exactly why “it works on my machine” reports diverge between colleagues.

Either quote style protects the ampersands. Single quotes are the safer habit, because inside double quotes a $ in the URL would still be expanded by the shell:

5.1 What the report tells you about a live multiplex

Real output from a DVB-S2 multistream transponder carrying a national public broadcaster’s services, abbreviated to the header and the service list:

Three readings from the service list that generalise to any multiplex:

5.2 The SAT>IP query parameters

The URL is the SAT>IP tuning API. Each parameter maps onto something in the other sheets on this shelf.

Why isi and plsc travelling together matters. A multistream carrier cannot be locked by frequency and symbol rate alone. If a transponder appears in a satellite directory with a PLS code listed, treat the stream identifier and the PLS pair as part of the tuning parameters, not as optional extras — a request missing them locks nothing, and the failure looks identical to a signal problem.

5.3 What this one request actually does

Those parameters are not an arbitrary list — read together they describe a five-stage pipeline, and this transponder happens to exercise every stage. It is the clearest single example of how raw BBFrame reconstruction and T2-MI de-encapsulation fit together in SAT>IP Servers Pro.

Stage 4 is the one that needs explaining, because it exists to work around a hardware limitation rather than to implement a standard.

Why raw BBFrame mode exists. On a multistream carrier the demodulator normally de-encapsulates MIS to transport stream in hardware. For a bursty T2-MI feed that hardware path drops packets — typically around 9 %. Nine percent sounds survivable until you remember what the T2-MI CRC covers: header, payload and padding, across the whole packet. Lose fragments at that rate and you get near-total T2-MI CRC failure and an undecodable inner multiplex while the RF measures perfect — no errored packets, good C/N, nothing wrong with the carrier. It is a genuinely confusing failure, because every RF indicator says the link is healthy.

bbframe=1 sidesteps it. On STiD135-based tuners with the raw-bbframe driver patch, the demodulator is told to emit raw DVB-S2 BBFrames instead of de-encapsulating them, and SAT>IP Servers Pro reconstructs the outer transport stream losslessly in software before handing it to the T2-MI demux. The hardware stops doing the thing it does badly.

The trap that costs an afternoon: plsm takes a name, not a number. Write plsm=gold&plsc=131070. A numeric plsm is interpreted as root mode, which then re-derives the code — so the tuner searches for a different scrambling sequence than the one you meant and never locks. The failure looks exactly like a wrong PLS code or a weak signal, and the parameter you would most naturally suspect is the one that is right.

This is also why the analysis at the top of this section is worth running as an acceptance test rather than only when something breaks. Zero invalid sync, zero transport errors and zero unreferenced PIDs across a five-second window is positive evidence that all five stages are working — the carrier locked, the right stream was selected, the physical layer descrambled, the outer TS reconstructed without loss, and the inner multiplex de-encapsulated cleanly. Any one of those failing shows up in the report rather than in a complaint from a customer.

5.4 Bounding a live capture

A live input never ends, so an unbounded tsp runs until you interrupt it — and analyze prints its report only at termination. Two plugins make it terminate on purpose.

Why --seconds 5 reported a broadcast time of 4 seconds. They measure different things. until --seconds counts five seconds of wall clock from the first packet; the broadcast time in the report is derived from the stream’s own clock across the packets that actually arrived. Connection setup, the server’s own tuning delay before it starts sending, and the difference between wall-clock and PCR-derived duration all land in that gap. Nothing was lost. If you need the two to agree, use --pcr-based or count packets instead of seconds.

5.5 Getting service names right

Service names on European satellites are frequently signalled with an encoding the standard does not strictly permit, and the result is mangled accented characters. TSDuck has a shortcut for exactly this:

--europe is a synonym for --dvb --default-charset ISO-8859-15, described in the plugin’s own help as a handy shortcut for commonly incorrect signalling on some European satellites. If service names come back with question marks or wrong accents, try it before assuming the broadcaster’s signalling is broken. Equivalent presets exist for other regions — --japan, --brazil, --china, --atsc, --isdb, --abnt.

5.6 Running it continuously

For monitoring rather than a one-off inventory, drop until and let analyze emit reports on a schedule.

For an unattended monitor, the HTTP input’s own resilience options matter as much as the analysis:

A note on analyze versus tsanalyze. They produce the same report, but -P analyze is a pipeline stage, so it can sit anywhere in the chain and measure the stream at that point. Putting it before a zap and again after it tells you what the filtering actually removed. tsanalyze is the convenience command for the common case of measuring an input and nothing else. Order matters here as everywhere in tsp: in the command at the top of this section analyze precedes until, so it counts every packet that flows before termination.

6. Analysis a script can read

The boxed report is for humans. For monitoring, two other formats exist and both are stable enough to build on.

The normalized form is one record per line, each beginning with a record type. Extracting a single field is a one-liner, which makes it the better choice inside a shell-based check:

A trap that cost real time while writing this page. tsanalyze --json writes JSON to standard output, but several other TSDuck commands spell the equivalent option --json-output file-name, which takes a filename argument. Because TSDuck accepts unambiguous option abbreviations, writing tstables --json demo.ts is resolved as --json-output demo.ts — and your input file is overwritten with the JSON result. In this case that was a two-byte empty array, and every subsequent command in the session reported an empty stream. Prefer the long form, and never let the output filename position be occupied by your input.

7. Reading the signalling

tstables collects PSI/SI and prints it decoded. Select by PID or by table id, and cap the collection so it terminates on a live input.

Selecting by table id rather than PID is usually what you want, because it finds the table wherever it lives:

A PMT from the demo stream shows why the PSI/SI sheet insists that stream type and codec are different questions:

PID 530 has stream_type 0x06 — “private data”, which says nothing. The AC-3 descriptor is what identifies it as audio. Reading only the stream type would leave you thinking this service has no audio at all.

For structured output, use the explicit long options with real filenames:

8. Tables as editable XML

This is the capability that most distinguishes TSDuck from a viewer. Tables round-trip losslessly between binary sections and XML, so signalling becomes something you can edit in a text editor and put back.

The XML dialect is strict and its schema ships with the install. When a table fails to compile, the error names the offending node and line, which is usually enough:

That was a real error from building this page’s demo stream. The correct child element is <subtitling language_code="eng" …>, not <language code="eng" …>. Rather than guess, read the shipped model:

Read the model file, not a tutorial. It is generated from the same definitions the compiler uses, so it cannot drift from the implementation, and it covers every table and descriptor TSDuck knows. Any XML question about signalling is answered there faster than by searching.

9. Building a test stream from nothing

You do not need captured broadcast content to test a pipeline, and using it brings rights and privacy problems that a synthetic stream avoids entirely. TSDuck can generate a fully valid multi-programme stream from a few XML files. This is exactly how the demo stream on this page was made.

Step 1 — describe the signalling. One XML file per PID, since inject places one file’s sections on one PID:

Step 2 — craft the elementary streams. craft generates packets on one PID with a chosen payload pattern:

Step 3 — multiplex. Start from null packets, inject the signalling, mux in the elementary streams. --inter-packet sets how many packets of the outer stream sit between two packets of the new PID, which is how you control each component’s share of the multiplex:

Step 4 — make the clocks real. Crafted packets all carry the same PCR value, so bitrate cannot be derived and tsanalyze reports Estimated based on PCR's: Unknown. One pass fixes it:

Step 5 — make the continuity counters real. Crafted packets also share a continuity counter, which makes packet loss undetectable. Repair in place:

After those two passes the stream analyses as a genuine 38 Mb/s multiplex: PCR-derived bitrate, correct per-service rates, incrementing continuity counters, zero unreferenced PIDs. Both passes are easy to forget, and forgetting them produces a stream that looks fine in a hex dump but behaves nothing like broadcast under test.

10. Extracting one service from a multiplex

Three plugins do overlapping jobs here and the difference matters.

zap accepts a service name, which is far more readable than a PID list:

svremove paired with rmorphan is the combination to remember. Removing a service leaves its elementary PIDs referenced by nothing; rmorphan sweeps them up:

PIDs 768, 785 and 786 — the removed service’s PMT and elementary streams — are gone. Without rmorphan they would still be carried, consuming bandwidth while tsanalyze reported them as unreferenced.

--pid is not a comma-separated list. Writing --pid 0,17,256 fails with value for option --pid (-p) must be <= 8,191, because the whole string is parsed as one integer. The option takes a single PID or a pid1-pid2 range and is repeated for more:

tsp -I file demo.ts -P filter --pid 0 --pid 17 --pid 256 --pid 273-275 -O file svc1.ts

The same convention applies to every plugin whose help shows -p pid1[-pid2], which is most of them.

11. Rewriting signalling in flight

Renaming a service is a one-liner, and it edits the SDT rather than repacking anything:

The related plugins cover the rest of the signalling surface. Each performs targeted transformations rather than wholesale replacement, so unspecified fields are preserved:

Where this is genuinely useful. Regional feeds, service renumbering to fit a downstream platform’s expectations, and repairing a source whose signalling is legal but inconvenient. Because these plugins sit in a pipeline, the fix happens in flight — you are not storing a corrected copy and re-serving it.

12. Timing: PCR extraction and verification

pcrverify checks PCR jitter against a threshold and reports a single summary line, which makes it suitable for an automated check:

Note how the threshold is echoed: 100,000 microseconds is shown as 2,700,000 in 27 MHz units, the clock PCR is actually counted in. The “3 unchecked” are the first PCR on each of the three PCR-carrying PIDs — there is nothing to compare a first sample against.

For analysis rather than a pass/fail, pcrextract writes every timestamp to CSV:

Add --pts and --dts to include presentation and decode timestamps in the same file, which is how you investigate lip-sync complaints: compare PTS progression against the PCR that is meant to be pacing it.

13. Continuity counters, and proving the 16-packet blind spot

The MPEG-TS sheet states that the continuity counter is 4 bits, so it wraps every 16 packets, and that a loss of exactly 16 packets on a PID is therefore invisible. That is a claim worth testing rather than repeating, and TSDuck can test it directly.

The method: take the demo stream with correct continuity counters, drop a controlled number of consecutive packets, and ask tsanalyze what it noticed. PID 0x0111 appears roughly every fourth packet, so dropping n packets of the multiplex removes about n/4 packets of that PID.

The measured results:

The blind spot is real, and it is worse than usually described. Losing exactly 16 packets produces an observed continuity step of 1 — indistinguishable from a perfectly continuous stream. Zero discontinuities, zero duplicates, no indication whatsoever that 16 packets are missing. And a loss of 15 is reported as a duplicate rather than a loss, because the counter appears to repeat: a monitor that alarms on discontinuities but treats duplicates as benign will miss it too. Continuity counting detects loss only when the number of lost packets is not a multiple of 16, which means it is a useful signal but never a sufficient one. Byte-accurate accounting has to come from somewhere else — PCR jitter, bitrate deviation, or a transport-layer sequence number such as RTP’s.

The repair tool, for the case where continuity counters are wrong but packets are all present — a common artefact of naive remultiplexing:

Use --no-action first. If it reports a large number of corrections, the problem is upstream and rewriting the counters hides it rather than fixing it.

14. Monitoring a live stream

Three plugins pass packets through untouched while reporting, so they can be inserted into a production pipeline.

stats reports per-PID packet counts and inter-packet distance, which is the fastest way to see whether a component’s share of the multiplex is stable:

A standard deviation far above the mean’s neighbours indicates a bursty PID — which for a PCR-carrying PID is a direct cause of jitter downstream.

history logs structural events as they happen: PIDs appearing and disappearing, table versions changing, scrambling starting and stopping:

Add --milli-seconds to timestamp events in wall-clock rather than packet numbers. This is the plugin to reach for when a service intermittently disappears and you need to know whether the signalling changed or the packets simply stopped.

For dashboards, influx pushes live metrics to InfluxDB, which Grafana reads directly:

Two more worth knowing: iat analyses inter-arrival time for datagram inputs, which is the right measurement for diagnosing a network path rather than a stream; and tslatencymonitor compares two inputs to measure end-to-end latency between them.

15. Conditional access

TSDuck implements the scrambling side of the mechanisms in the conditional access sheet, which makes it useful for validating a CA integration without touching a live encrypted service.

The service’s access column flips from C to S while the other two remain clear — a self-contained way to produce a scrambled stream for testing a descrambling path. The control word above is a published test value, not a key from any real system.

On the inspection side, tstables --tid 0x01 reads the CAT, and the CA descriptors in each PMT identify the ECM PIDs. Both are in the clear in any stream, so you can map a CA deployment’s signalling without holding a single key.

16. T2-MI

Where a DVB-T2 modulator feed is carried inside a transport stream, the inner stream has to be de-encapsulated before anything else works. The MPEG-TS sheet covers the packet structure; the extraction is one plugin:

Without --extract the plugin analyses and reports on the T2-MI structure while passing the outer stream through. With it, the output becomes the inner transport stream and everything downstream in the pipeline operates on that instead.

The PLP selection is not optional in practice. A T2-MI feed can carry several Physical Layer Pipes, and extracting without specifying which one gives you whichever the plugin defaults to. If a service you expect is absent from the extracted stream, the PLP is the first thing to check — not the PID.

17. Inputs and outputs beyond files

The pipeline model pays off here: any input can feed any chain into any output, so TSDuck doubles as a protocol converter.

Always insert regulate when a file feeds a network output. Without it, tsp reads the file as fast as the disk allows and transmits at that rate — tens or hundreds of times real time. The receiver sees a burst it cannot buffer, and the resulting symptoms look like a network fault rather than a missing plugin.

The pcap input deserves particular note: it reads transport stream packets straight out of a Wireshark capture. When a stream problem has already happened and all you have is a capture, this turns that capture into something tsanalyze can measure.

18. Input failover

tsswitch is a separate command rather than a plugin, because switching between inputs is not something a linear pipeline expresses. It takes several inputs and delivers one output, switching on failure.

--fast-switch is the option that determines whether failover is seamless or merely automatic. Without it, the backup input is only started once the primary has been declared dead, so the gap includes the backup’s own startup time — for a satellite receiver, potentially seconds. With it, both inputs run continuously and the switch is a decision about which buffer to read from.

Where this fits SATLINE’s architecture. A SAT>IP tuner delivered from inside a satellite footprint is an IP input like any other, so a facility can run two independent tuner sources and let tsswitch arbitrate. That is redundancy across receiving sites rather than across tuner cards in one chassis — which is the failure mode that actually takes services off air. See the locked and unlocked tuners sheet for how the sources differ.

19. Deliberately breaking a stream

Monitoring that has never seen a fault is untested monitoring. fuzz introduces controlled corruption:

Payload corruption does not set the transport error indicator. Running fuzz with only --corrupt-probability and then checking tsanalyze for transport errors reports zero — because corrupting payload bytes damages content without touching the header flag that signals a known-bad packet. That flag is set by demodulators when FEC fails, not by whatever mangled the bytes. To produce header-level faults, add --sync-byte. This distinction is exactly why the DVB errors sheet treats sync loss and content corruption as different priority levels.

For precise rather than random damage, slice drops or nulls packets at exact positions — which is how the continuity measurements in section 13 were produced:

The difference matters: dropping packets changes the bitrate, while nulling them preserves it. For testing a constant-bitrate path, null rather than drop.

20. Traps, all encountered while writing this page

These are not hypothetical. Each one produced a wrong or confusing result during the preparation of this sheet.

The general lesson. TSDuck’s option abbreviation is convenient and occasionally dangerous, and its plugins do not share a common vocabulary — what one calls --probability another calls --corrupt-probability, and --pid means different things depending on the plugin. tsp -P <plugin> --help is authoritative, takes two seconds, and would have prevented every row in that table.

Gleb Sazanov

Chief Technology Officer

Gleb Sazanov is an accomplished Chief Technology Officer (CTO) with over 20 years of experience in software development, system architecture, and cloud-based solutions. As the CTO of SATLINE, a leading provider of virtual and colocation services tailored to SATCOM businesses, Gleb drives the company’s technological strategy, fostering innovation and efficiency in data center services. His expertise spans various domains, including DevOps, system scaling, and high-performance infrastructure management. With a deep passion for cutting-edge technologies, Gleb plays a pivotal role in shaping the future of the SATCOM industry.