TSDuck Cheat Sheet
The commands you actually reach for, all executed against TSDuck 3.44-4676 with real output. One pipeline shape, twenty recipes, and the traps that waste an afternoon.
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
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 |
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.
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
&, 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.
-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 |
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 |
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 |
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 |
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 |
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
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.
--pid is 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-b for ranges: --pid 0 --pid 17 --pid 273-275.
It is pcrverify --jitter-max, not --time-limit. It is fuzz --corrupt-probability, not --probability. slice takes --drop/--pass/--null and has no --pid at all.
Bitrate reads as Unknown and dropped packets produce zero discontinuities. Run pcradjust then tsfixcc before treating a generated stream as broadcast-like.
fuzz --corrupt-probability aloneThe transport error indicator is set by demodulators when FEC fails, not by whatever mangled the bytes. Add --sync-byte for header-level faults, and --seed so a failure you find can be replayed.
tsp -P <plugin> --help is authoritative, takes two seconds, and would have prevented every one.