Documentation
TSDuck Cheat Sheet
Quick reference guide to everyday TSDuck commands, tested on version 3.44, from stream analysis and table editing to timing checks and failover.
- 2026-10-09 12:59
- 2026-10-13 13:04
- The whole model-I -P -O
- Commands in 3.4443
- Packets lost = invisible16
- Always authoritative--help
The pipeline
One input, any number of processors, one output. Packets flow left to right; each stage sees what the previous one left.
tsp -I file in.ts -P plugin1 -P plugin2 -O file out.ts
Order matters. Filtering before analysing answers a different question from analysing before filtering. And plugins like analyze, history, stats and pcrverify pass packets through untouched — so you can instrument a live pipeline without changing what it delivers.
Look at an unknown stream
| Command | Answers |
|---|---|
tsanalyze in.ts | Everything — services, PIDs, bitrate, errors, scrambling |
tsanalyze --service-list in.ts | Decimal service IDs, one line, pipeline-friendly |
tsanalyze --pid-list in.ts | Every PID present, including 8191 (null) |
tsanalyze --normalized in.ts | key=value records a script can parse |
tsanalyze --json in.ts | JSON to stdout |
tsbitrate in.ts | Just the bitrate, 188- and 204-byte |
tsdump --raw --max-packets 1 in.ts | Hex of the packet, header first |
The number to look at first is Unreferenced. Non-zero means PIDs carry data no PMT declares — bandwidth you are paying for and nothing can play. Second is Scrambled: check it before troubleshooting a CAM.
188 vs 204 bytes: the same stream with and without Reed-Solomon parity. Quote 204 for a transponder, 188 for an IP link.
Live stream over HTTP — the everyday command
Analysing a live SAT>IP feed is the most-used recipe there is. Note the quotes.
tsp -I http 'http://host:8875/?freq=12606&sr=35300&pol=V&msys=dvbs2&mtype=8psk&fec=23&isi=5&plsc=131070&plsm=gold&t2mi_pid=auto&t2mi_plp=all&pids=all' -P analyze -P until --seconds 5 -O drop
⚠️ Quote the URL — this is the number-one mistake. A SAT>IP URL is full of &, which is the shell’s background operator. Unquoted on bash the command is split at the first &: tsp receives only ?freq=…, the rest become shell variables, and the command is backgrounded so your prompt returns as if it succeeded. The server gets a frequency with no symbol rate or polarisation and cannot tune — so it presents as a signal fault that never left your shell. On zsh it is a loud parse error near '&' instead. Prefer single quotes: a $ is still expanded inside double quotes.
Also: -O drop is not optional — without an output plugin the packets flood your terminal. And -P until is what makes a live input terminate so analyze ever prints its report; --seconds is wall clock, --packets is deterministic.
| Parameter | Why it is there |
|---|---|
isi= | Multistream carrier — selects one of several streams sharing it. Omit it and you get nothing usable |
plsm=gold plsc= | Physical-layer scrambling, required with isi. plsm takes a name, not a number — a numeric value is read as root mode and never locks |
bbframe=1 | Demod emits raw DVB-S2 BBFrames; the server rebuilds the outer TS in software. Fixes the ~9% hardware de-encapsulation loss that causes near-total T2-MI CRC failure on a perfect carrier |
t2mi_pid=auto | De-encapsulate T2-MI (tries 4096, then 4095). Output becomes the inner TS |
t2mi_plp=all | Merge every PLP — right when signalling is in a common PLP and content in others |
pids=all | Full MPTS. Client-controlled per session; nothing is filtered for you unless you ask |
Reading a live report: Unreferenced should be 0. Fewer PCR PIDs than services means services share a PCR — normal. Several services at exactly the same bitrate means those carry only signalling, not content. And use --europe if service names come back with mangled accents (it is --dvb --default-charset ISO-8859-15).
Read and edit the signalling
| Command | Does |
|---|---|
tstables --tid 0x00 in.ts | PAT. Use 0x02 for PMTs, 0x42 for SDT, 0x01 for CAT |
tstables --pid 0 --max-tables 1 in.ts | By PID, capped so it terminates on a live input |
tstables --json-output t.json in.ts | Structured output — needs a real filename |
tstables --log-json-line in.ts | One JSON object per line, on stdout |
tstabcomp -d t.bin -o t.xml | Binary sections → editable XML |
tstabcomp -c t.xml -o t.bin | XML → binary sections |
When table XML will not compile, read the shipped schema rather than guessing: find / -name tsduck.tables.model.xml. It is generated from the same definitions the compiler uses, so it cannot be out of date.
Cut a multiplex down
| Plugin | Behaviour |
|---|---|
zap 'SERVICE NAME' | Keep one service, rewrite PAT and PMT → a valid SPTS |
svremove 'NAME' | Drop named services, leave the rest untouched |
rmorphan | Sweep up PIDs nothing references any more |
filter --pid N | Raw PIDs, no understanding of services |
svrename 'OLD' --name 'NEW' | Rename in the SDT, nothing repacked |
Always pair svremove with rmorphan. Removing a service leaves its elementary PIDs orphaned — still carried, still costing bandwidth: tsp -I file in.ts -P svremove 'OLD SVC' -P rmorphan -O file out.ts
Timing
| Plugin | Use |
|---|---|
pcrverify --jitter-max 100000 | Pass/fail on PCR jitter, in microseconds. One summary line |
pcrextract --pcr --pts --dts --output-file p.csv | Every timestamp to CSV — the lip-sync investigation tool |
pcradjust | Rewrite PCRs consistent with a constant bitrate |
pcrbitrate | Recompute bitrate live so later plugins see a real value |
regulate | Pace output to real time. Mandatory for file → network |
Forget regulate and you transmit a file as fast as the disk allows — hundreds of times real time. The receiver’s symptoms look exactly like a network fault.
The continuity counter blind spot — measured
The CC field is 4 bits, so it wraps every 16 packets. Dropping a controlled number of packets and asking tsanalyze what it noticed:
| Lost on the PID | mod 16 | CC step seen | Discontinuities | Duplicates |
|---|---|---|---|---|
| 16 | 0 | 1 — looks perfect | 0 | 0 |
| 17 | 1 | 2 | 1 | 0 |
| 15 | 15 | 0 — looks repeated | 0 | 1 |
| 11 | 11 | 12 | 1 | 0 |
Losing exactly 16 packets is completely invisible. Zero discontinuities, zero duplicates, no indication at all. And a loss of 15 is reported as a duplicate, which a monitor treating duplicates as benign also misses. Continuity counting detects loss only when the count is not a multiple of 16 — useful, never sufficient. Byte-accurate accounting has to come from PCR jitter, bitrate deviation, or an RTP sequence number.
Repairing bad counters: tsfixcc in.ts fixes in place. Run tsfixcc --no-action in.ts first — many corrections means the real problem is upstream, and rewriting hides it.
Monitor, convert, fail over
| Task | Command |
|---|---|
| Per-PID rate stability | tsp -I file in.ts -P stats -O drop |
| Structural event log | tsp -I … -P history --milli-seconds -O drop |
| Metrics to Grafana | -P influx --org o --bucket b --interval 5 |
| File → multicast | tsp -I file in.ts -P regulate -O ip 239.0.0.1:1234 |
| Multicast → SRT | tsp -I ip 239.0.0.1:1234 -O srt --caller host:9000 --latency 200 |
| HLS → TS | tsp -I hls https://…/master.m3u8 -O file out.ts |
| Analyse a Wireshark capture | tspcap cap.pcapng |
| Extract T2-MI inner stream | -P t2mi --extract --pid 4096 --plp 0 |
| Scramble for testing | -P scrambler --dvb-cissa --cw <32 hex digits> 'SVC' |
| Dual-input failover | tsswitch --primary-input 0 --fast-switch -I … -I … -O … |
--fast-switch is what makes failover seamless rather than merely automatic. Without it the backup only starts once the primary is declared dead, so the gap includes the backup’s startup time.
The traps
-
An abbreviated option overwrote the input file
tstables --json in.tsResolved as
--json-output in.ts, which takes a filename — so the input was overwritten with a 4-byte empty array. Every later command then reported an empty stream. Use full option names; never let an input path sit where an output filename goes. -
--pidis not a comma list--pid 0,17,256→ “must be <= 8,191”The whole string parses as one integer. Repeat the option, and use
a-bfor ranges:--pid 0 --pid 17 --pid 273-275. -
Plugins do not share a vocabulary
Guessed names fail outright
It is
pcrverify --jitter-max, not--time-limit. It isfuzz --corrupt-probability, not--probability.slicetakes--drop/--pass/--nulland has no--pidat all. -
A crafted stream is not a realistic stream
Same PCR, same CC on every packet
Bitrate reads as
Unknownand dropped packets produce zero discontinuities. Runpcradjustthentsfixccbefore treating a generated stream as broadcast-like. -
Payload corruption sets no error flag
fuzz --corrupt-probabilityaloneThe transport error indicator is set by demodulators when FEC fails, not by whatever mangled the bytes. Add
--sync-bytefor header-level faults, and--seedso a failure you find can be replayed.
All five were hit while writing this sheet. tsp -P <plugin> --help is authoritative, takes two seconds, and would have prevented every one.
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.