Preface
TSDuck is a free and open-source toolkit to manipulate MPEG Transport Streams.
This document is the TSDuck Developer Guide. It is used by developers of third-party C++, Python, or Java applications which use the TSDuck library.
As a product, TSDuck provides many different command-line tools and plugins.
Internally, the architecture of TSDuck is made of a large shareable library
(tsduck.dll on Windows, libtsduck.so on Linux and BSD systems,
libtsduck.dylib on macOS). Most of the valuable processing is performed by
the various C++ classes from the TSDuck library. All command-line tools and
plugins are usually small wrappers around this library.
The TSDuck library offers generic C++ classes as well as specialized classes for Digital TV. They can be used by any application to manipulate MPEG transport streams, the signalization, and various data which are carried in MPEG-TS. This library can be used as a general-purpose C++ library for third-party applications, outside the TSDuck tools and plugins.
A subset of high-level features is also accessible from Java and Python applications.
The following figure illustrates the TSDuck software architecture and how it interacts with third-party applications.
Structure of this guide:
-
The chapter 1 describes the various TSDuck libraries.
-
The chapter 2 explains how to build an application using TSDuck.
-
The chapter 3 is an overview of the main features of the TSDuck library.
-
The chapter 4 is a detailed description of the reactor pattern and event dispatching.
-
The chapter 5 is an overview of the Java and Python bindings for TSDuck.
-
The chapter 6 describes the development of TSDuck plugins.
-
The chapter 7 describes the development of TSDuck extensions.
In addition to this developer guide, a comprehensive reference programming documentation is available online. It documents all TSDuck public C++ classes, as well as the Python and Java API’s. This is a reference documentation which is automatically generated from the source files using doxygen.
License
TSDuck is released under the terms of the license which is commonly referred to as "BSD 2-Clause License" or "Simplified BSD License" or "FreeBSD License". This is a liberal license which allows TSDuck to be used in a large number of environments. See the appendix B for more details.
Documentation format
The TSDuck guides are built using asciidoctor, from a
set of text files which are maintained alongside the source code, in the same
git repository.
This guide is formatted for HTML. The file tsduck-dev.html is monolithic and self-sufficient, without reference to external images. Therefore, this HTML file can be downloaded, saved, and copied, as long as the license and content are not modified.
A PDF version tsduck-dev.pdf is also available. However, due to limitations in the PDF generator of asciidoctor, the rendering is sometimes not as good as the HTML document.
Documentation set
The TSDuck documentation set is made of:
-
TSDuck User Guide, using TSDuck commands and plugins (also from tsduck.io and in PDF format)
-
TSDuck Builder Guide, building and installing TSDuck (also from tsduck.io and in PDF format)
-
TSDuck Developer Guide, using TSDuck from C++, Python, Java applications (also from tsduck.io and in PDF format)
-
TSDuck Contributor Guide, contributing to TSDuck development (also from tsduck.io and in PDF format)
-
TSDuck Programming Reference, Doxygen-generated reference of all TSDuck classes.
1. Two libraries: TSCore and TSDuck
Starting with version 3.40, the TSDuck library has been split in two parts:
-
The TSCore library contains generic C++ classes and features which are not specific to Digital TV.
-
The TSDuck library contains all Digital TV classes and features. It depends on the TSCore library for basic features.
Although it does not compete with other widely used C++ utility libraries, the TSCore library can be useful to any type of application. It include features for networking, text processing (including XML and JSON handling), advanced strings, unified logging and reporting, data serialization and deserialization, etc.
Before the split, the size of the TSDuck library became quite large. Most of it was made of C++ classes to handle Digital TV features, including nearly 400 classes to handle tables and descriptors which are defined by the various Digital TV standards. In addition to their large memory footprint, these classes self-register themselves at the initialization of the TSDuck library, slowing down the application startup on small systems.
For applications which only used the basic features of the TSDuck library, the Digital TV features became too bulky. Therefore, these basic features were extracted as a new TSCore library.
The TSCore shared library file is named libtscore.so, libtscore.dylib, or
tscore.dll, depending on the operating system. The TSDuck shared library file
is named libtsduck.so, libtsduck.dylib, or tsduck.dll,
Their respective header files are installed in directories include/tscore and
include/tsduck (the exact location of the include parent directory depends
on the operating system).
Therefore, the two development environments are separated. The next chapters explain in details how to use each of them.
Since the TSDuck library uses the TSCore library, any application which uses the former has implicit access to the latter. Specifically, applications which already used the monolythic TSDuck library before version 3.40 can be built without any change.
Starting with version 3.42, TSDuck installs a third common shared library named
libtsdektec. This library contains the common code to access the Dektec
devices, as well as the Dektec-provided DTAPI proprietary code. It is only
designed to provide common code for the tsdektec command and the two dektec
plugins, input and output. This library is not accessible to third-party
applications.
This library libtsdektec was extracted from the main TSDuck library as a
response to users who preferred to separate free and non-free code. Thus, when
required, removing libtsdektec, as well as the tsdektec command and the
tsplugin_dektec shared library, is sufficient to remove all non-free code from
a TSDuck installation.
2. Building an application with the TSDuck libraries
2.1. Pre-requisites
To be able to build applications or tsp plugins with the TSDuck libraries,
you must install the TSDuck development environment first.
-
On Windows systems, you must select the optional "Development" component during the installation.
-
On Fedora, Red Hat and clones, you must install the package
tsduck-devel. -
On Ubuntu and the Debian family, you must install the package
tsduck-dev. -
On macOS systems, the development environment is always installed with TSDuck using Homebrew.
-
If you build TSDuck from sources, use
make install(which is equivalent tomake install-tools install-devel).
2.2. Building applications on UNIX systems (Linux, macOS, BSD)
The command tsconfig generates the appropriate build options for the current operating system.
See the TSDuck user guide for more details on tsconfig.
The following sample makefile illustrates the creation of a simple
application named myexec using one single source file myexec.cpp.
CXXFLAGS += $(shell tsconfig --cflags)
LDLIBS += $(shell tsconfig --libs)
default: myexec
This is as simple as that.
Just run make to build the application.
$ make
As explained in the user guide, use the tsconfig option --tscore if you want to
use the TSCore library only, not the full TSDuck library for Digital TV.
For instance:
CXXFLAGS += $(shell tsconfig --tscore --cflags)
LDLIBS += $(shell tsconfig --tscore --libs)
C++ language level
By default, the command tsconfig --cflags forces C++20 as level of C++ language standard.
If your application requires a more recent level, define the environment variable TS_NOSTDCPP to any non-empty value.
This disables the C++ standard option in tsconfig.
The application shall then define its own C++ standard in its command line.
This user-specified C++ standard cannot be lower than C++20.
Alternatively, the command tsconfig --nostdcpp --cflags can be used to omit the C++ standard
from the compilation options without defining the environment variable TS_NOSTDCPP.
2.3. Building applications on Windows
The "Development" option of the TSDuck installer provides the build environment for Visual Studio 2022, in debug and release mode, for 64-bit Intel platforms. It may be compatible with Visual Studio 2019 or earlier, but without guarantee.
The environment variable TSDUCK is defined to the root of the TSDuck installation tree.
A Visual Studio property file named tsduck.props is installed here.
It provides all definitions and options to use the TSDuck libraries.
Create the solution and projects for your application.
Then, manually edit the project file, named for instance app.vcxproj,
and insert the following line just before the final </Project> closing tag:
<Import Project="$(TSDUCK)\tsduck.props"/>
Then build your project normally.
If you want to use the TSCore library only, not the full TSDuck library for Digital TV,
use the file tscore.props instead:
<Import Project="$(TSDUCK)\tscore.props"/>
C++ language level
By default, the property file tsduck.props forces C++20 as level of C++ language standard.
If your application requires a more recent level, define the environment variable TS_NOSTDCPP to any non-empty value.
This disables the C++ standard option in tsduck.props.
The application shall then define its own C++ standard in its project files.
This user-specified C++ standard cannot be lower than C++20.
3. Overview of the TSDuck libraries
The TSDuck libraries contain general-purpose C++ classes and utilities to handle MPEG transport streams.
For programming details, see the programming reference documentation online (doxygen-generated).
Roughly, the TSDuck libraries provide two categories of features:
-
Operating system abstraction layer to make the application code fully portable between heterogeneous platforms. This is similar to frameworks such as Qt, but much more lightweight.
-
Handling of MPEG transport streams and signalization, including DVB, ATSC and ISDB features.
| In early versions of TSDuck, the OS abstraction layer contained many more classes. Starting with C++11 and C++20, the standard library of the language was enriched with many more system features. Some low-level TSDuck classes became obsolete. The code was migrated to use standard C++20 features and the corresponding low-level classes were removed from TSDuck. |
All C++ declarations are located inside the namespace ts, either directly within ts or inside inner namespaces.
All preprocessor’s macros are named with prefix TS_.
3.1. C++ features
3.1.1. Portability issues
The file tsPlatform.h contains some very low level definitions such as macros defining the environment
(processor, compiler, operating system, endianness), byte and bit manipulation, etc.
3.1.2. C++ strings
C and C++ strings are made of 8-bit characters which are notoriously unable to represent international character sets.
The usage of std::string with the TSDuck libraries is discouraged in favor of Unicode strings.
3.1.3. Unicode strings
The class ts::UString implements Java-like Unicode strings.
Each character uses 16 bits of storage.
Formally, ts::UString uses UTF-16 representation.
This means that all characters from all modern languages can be represented as one single character.
Characters from archaic languages may need two UTF-16 values, called a "surrogate pair".
Technically, ts::UString is a subclass of std::u16string.
So any operation on standard C++ strings is also available to ts::UString.
But many more operations have been added to manipulate Unicode strings.
For consistency, the type ts::UChar is an alias for char16_t.
The header file tsUChar.h defines some utility functions on ts::UChar.
It also defines constants for most Unicode characters like ts::COLON or more complex ones such as
ts::LATIN_CAPITAL_LETTER_A_WITH_ACUTE, among hundredths of others.
Some interesting features in class ts::UString are:
-
Explicit and implicit conversions between UTF-8 and UTF-16.
-
Including automatic conversion to UTF-8 when writing to text streams.
-
Conversions with DVB and ARIB character sets.
-
Conversions with HTML encoding.
-
Management of "display width", that is to say the amount of space which is used when the string is displayed. This can be different from the string length in the presence of combining diacritical characters or surrogate pairs.
-
String padding, trimming, truncation, justification, case conversions.
-
Substring, prefix or suffix detection, removal or substitution.
-
Splitting and joining strings based on separators or line widths.
-
Reading or writing text lines from or to a text file.
-
Data formatting using
format(),Format(),Decimal(),Hexa(),Dump(). -
Data scanning using
scan().
Unicode strings can be converted to and from DVB or ARIB (Japan) strings.
Most DVB-defined character sets are implemented (see the classes ts::Charset and ts::DVBCharset)
and recognized when a string is read from a descriptor.
When a string is serialized into a binary descriptor, the most appropriate DVB character set is used.
In practice, a few known DVB character sets are used and, when the string cannot be encoded in any of them,
UTF-8 is used (UTF-8 is always a valid DVB character set).
3.1.4. Binary data
The class ts::ByteBlock represents a raw block of bytes.
It is a subclass of std::vector<uint8_t> and consequently benefits from all standard vector operations.
It also adds useful methods for data serialization or deserialization in any byte order.
For data serialization or deserialization over arbitrary memory areas,
the header file tsMemory.h provides low-level functions to access integer values
of 8, 16, 24, 32, 40, 48 and 64 bits in any byte order.
The class ts::Buffer provides a higher-level abstraction layer over a memory area to parse or generate bitstreams.
It gives access to data of any bit-size at any bit position, any endianness,
either as a continuous stream or seeking at random bit positions.
The principles of the C++ class ts::Buffer were freely inspired by the Java class java.nio.ByteBuffer.
There are differences between the two but the main principles are similar.
Its subclass ts::PSIBuffer provides primitives to serialize and deserialize MPEG and DVB structures
such as list of descriptors, DVB, ARIB and ATSC strings or "Modified Julian Dates".
3.1.5. Singletons and static data
The singleton design pattern is simple in theory, but not so simple to implement correctly in practice.
The TSCore library encapsulates the implementation difficulties using the two macros TS_SINGLETON()
(in a header file) and TS_DEFINE_SINGLETON() (in the corresponding compilation unit).
In practice, the singleton design pattern is a specific case of global static data, variables which are defined outside any function. Using static data can be a nightmare because it is impossible to manage the initialization order of modules in C++.
In the past, how to properly managed singletons and static data was quite difficult, involving dangerous pointer and thread synchronization tricks. This has been abundantly documented by Scott Meyers (see [MEYERS-EFF]).
In modern C++, most of these difficulties have been solved in the language using local static data. In practice, the same tricks are used but they are now hidden inside the compiler and the run-time library.
3.1.6. Error reporting
All TSDuck classes use a consistent error reporting mechanism through the ts::Report abstract class.
This interface defines several levels of severity in the type ts::Severity,
ranging from ts::Severity::Debug to ts::Severity::Fatal.
Each instance of ts::Report defines which levels of message are reported to the user.
This is usually triggered by command-line options such as --verbose or --debug.
Most classes or methods from the TSDuck libraries use a reference to an instance of ts::Report to report messages and errors.
The actual reporting object is often built at application level and then propagated to all layers of code.
Some interesting subclasses of ts::Report are:
-
ts::CerrReport, a singleton which reports errors tostd::cerr. The macroCERRcan be used as a shortcut to the instance of the singleton. -
ts::NullReport, a singleton which drops all messages. The macroNULLREPcan be used as a shortcut to the instance of the singleton. -
ts::ReportFilewhich logs messages in a file. It can be made thread-safe using ats::ThreadSafetyvalue as template argument. -
ts::ReportBufferwhich logs messages in a memory buffer. It can be made thread-safe using ats::ThreadSafetyvalue as template argument. -
ts::Args(see section 3.1.9) which defines the syntax and handling of command line arguments. This is the typical instance ofts::Reportwhich is used at application-level. -
ts::Plugin, the superclass of alltspplugins. A plugin reports its messages directly in its own instance. Eachtspplugin executes in a separate thread and asynchronously logs messages without slowing down the plugin’s thread.
3.1.7. Exceptions
As a general rule, TSDuck prefers the usage of error reporting interface and error status over exceptions. However, for a limited number of unrecoverable conditions which should never occur in practice, exceptions are used.
All TSDuck exceptions inherit from the superclass ts::Exception.
An instance of this exception is able to embed an error message and an optional system error code.
Each specific exception should be a subclass of ts::Exception.
Instead of rewriting the subclass code, applications should use the macro TS_DECLARE_EXCEPTION().
3.1.8. Pseudo-enumeration data
An instance of the class ts::Enumeration associates a list of integer or enum values with strings.
It can be used to display meaningful strings instead of integer values.
It is even more useful to decode command line arguments.
When an option accepts a predefined list of values, the input string can be either an integer value or a name.
When it is a name, it can even be abbreviated as long as it is not ambiguous in the corresponding ts::Enumeration.
This is transparent for the application which receives the corresponding integer value.
3.1.9. Command-line arguments
The class ts::Args implements a generic handling of command line arguments.
Each application typically defines its own subclass of ts::Args.
A plugin is always a subclass of ts::Args, through the intermediate class ts::Plugin.
A subclass of ts::Args defines the command line syntax and the corresponding help text.
The superclass ts::Args automatically parses the command line, reports errors and
handle common options such as --help or --version.
The value of command line options can be free strings, integer values or enumeration values.
Integer values are recognized in decimal or hexadecimal form (prefix 0x) and
thousands separators (‘,’) which are present for clarity are ignored.
Enumeration values are handled through ts::Enumeration.
3.1.10. XML data
The TSCore library embeds an XML parser and several classes to handle a DOM structure.
See the class ts::xml::Node, the abstract base class of the DOM hierarchy.
3.1.11. JSON data
The TSCore library embeds a JSON parser and several classes to handle JSON values.
See the class ts::json::Value, the abstract base class of the JSON hierarchy.
3.2. Cryptography
The TSDuck libraries contain a few cryptographic classes. These libraries are not cryptographic libraries and will never be. Cryptography is a serious matter which should be left to cryptographers.
Some transport stream processing operations require cryptographic operations, essentially block ciphers and hash functions. The TSCore library proposes an homogeneous API over them. Standard cryptographic primitives are implemented using the standard system libraries, OpenSSL on UNIX systems (Linux, macOS, BSD), BCrypt on Windows. Most standard chaining modes are also implemented in the standard system libraries. The TSCore library implements more exotic chaining modes only.
Specific algorithms which are defined by Digital TV standard bodies (DVB, SCTE or IDSA) are implemented in the TSDuck library. However, most of them are only specific modes over standard cryptographic primitives such as AES or DES. The TSDuck library implements the specific modes but the primitives are used from the system cryptographic libraries, except the very specific DVB-CSA2 algorithm which is directly implemented in the TSDuck library.
The abstract class ts::BlockCipher is the root of a hierarchy of symmetric cryptography classes,
including chaining modes.
The main block cipher classes are ts::AES128, ts::AES256, ts::TDES and ts::DES.
| DES is an obsolete and insecure algorithm. TDES (a.k.a. 3-DES or Triple DES) is also deprecated. However, the two are still used in some legacy ATSC Digital TV systems. |
Chaining modes are template classes which inherit from the abstract class ts::CipherChaining.
The template parameter is a block cipher class.
The main chaining modes are ts::ECB, ts::CBC, various flavors of ts::CTSx
or more exotic modes from the DTV world such as ts::DVS042.
Additionally, ts::CipherChaining is also a subclass of ts::BlockCipher because it remains a symmetric cipher.
So, ciphers like ts::AES or ts::CBC<ts::AES> can be used through the same ts::BlockCipher interface.
The class ts::Scrambling implements DVB-CSA-2, the Digital Video Broadcasting Common Scrambling Algorithm.
This implementation is older than the open-source libdvbcsa library
and is probably less efficient.
The abstract class ts::Hash is the root of a hierarchy of hash functions classes.
The main hash functions are ts::SHA1, ts::SHA256 or ts::SHA512.
The abstract class ts::RandomGenerator is the root of pseudo-random generators.
The subclass ts::SystemRandomGenerator is a portable interface to the system-provided PRNG.
Usually, this is not the best PRNG on earth, but it is fine for most usages in TSDuck applications.
For more critical usages (such as encryption key generation), use ts::BetterSystemRandomGenerator.
This PRNG class uses ts::SystemRandomGenerator with an additional security layer.
The class ts::Xoshiro256ss implements the Xoshiro256** PRNG.
It is a fast and deterministic PRNG, with a low level of security.
The same seed will always produce the same pseudo-random sequence.
It can be used in cases where many random numbers are required, without strong security criteria.
It is typically used in fuzzing tools.
3.3. Operating system features
3.3.1. Miscelleaneous system utilities
The header files tsSysUtils.h, tsFileUtils.h, tsEnvironment.h,
declare utility functions on top of the operating system.
With the introduction of C++20, many of these functions have been removed in favor of new standard functions. However, a number of additional features manipulate:
-
File paths.
-
File attributes.
-
Creating or deleting files and directories.
-
Environment variables.
-
Process identifiers.
-
System error codes.
3.3.2. Time
The class ts::Time is a portable implementation of time (both local and UTC time).
Many operations are provided, such as:
-
Getting system time in various forms.
-
Arithmetic operations on time.
-
Analysing and building time values.
-
Formatting time values as strings.
3.3.3. Multithreading
TSDuck is heavily multi-threaded. The abstract class ts::Thread manages a
thread. To define an actual thread, derive this class and implement the virtual
method main().
The class ts::ThreadAttributes contains all mandatory or optional
attributes of a thead. An application typically builds a
ts::ThreadAttributes object and then creates threads using these
attributes.
In earlier versions of TSDuck, synchronization primitives used to be implemented
through specific classes (ts::Mutex, ts::Condition). They are now removed
and new C++11 classes such as std::mutex and std::condition_variable are
used instead.
Note that the C++11 class std::thread is not used. Its API is too limited
to be useable in complex environments: it does not allow to customize the
priority or the stack size before the creation of the thread. Therefore,
TSDuck exclusively uses ts::Thread and ts::ThreadAttributes
instead.
TSDuck relies on C++ mechanisms to track the usage of resources.
Standard classes such as std::lock_guard or std::unique_lock are used
to ensure that no dangling lock is lost through the guard design pattern.
In addition to multithreading, TSDuck also uses event dispatching. See chapter 4 for more details.
3.3.4. Virtual memory
The class ts::ResidentBuffer implements a buffer which is locked in
physical memory, preventing paging or swapping on this buffer. This is useful
for large data buffers with high performance constraints.
This is a template class. The template parameter is the type of the elementary data in the buffer.
The core data of the tsp processor is a ts::ResidentBuffer<ts::TSPacket>.
The incoming packets are directly written into this buffer by the input plugin.
Each packet processing plugin directly reads and writes the packets here. And
the output plugin reads the packet there, at the very same place they were
written by the input plugin. Given that this global buffer is locked in
physical memory, the best performances are guaranteed.
Note however that most operating systems require that the application has privileges to lock physical memory.
3.3.5. Processes
To track potential memory leaks and the impact of the application on the system,
the class ts::SystemMonitor creates a background thread which reports the
process metrics of the application at regular intervals.
The class ts::ForkPipe is a portable and convenient way to create a
process running a specific command and creates an outgoing pipe from the calling
application to the standard input of the created process, or an incoming pipe
from the standard output of the created process to the calling application, or
both. The pipes are open in binary mode (when it makes sense for the operating
system) and can be used to pass an entire transport stream when necessary.
3.3.6. Networking
The classes ts::IPAddress and ts::IPSocketAddress define an IP
address and a corresponding socket address (an IP address and a port number).
Host name resolution and multicast are supported.
The class ts::IPAddress can hold IPv4 and IPv6 addresses. All network
operations on name resolutions and TCP or UDP sockets are transparently
supported for IPv4 and IPv6 addresses.
The classes ts::TCPSocket and ts::UDPSocket implement TCP/IP and
UDP/IP endpoints.
The class ts::UDPSocket can be used directly to send and receive
datagrams. Multicast is supported.
The class ts::TCPSocket can be used only through two subclasses. The
subclass ts::TCPConnection is a TCP/IP communication endpoint, either on
client or server side. It is used to send or receive data streams. The
subclass ts::TCPServer is used to implement a TCP server. It accepts
incoming client connections and initiates a ts::TCPConnection for each
new connection. On the client side, the class ts::TCPConnection is
directly used to connect to the server.
The class ts::WebRequest performs simple Web requests using HTTP, HTTPS
or FTP. Using a URL, the result can be downloaded in memory or in a file.
Multiple redirections and SSL/TLS are automatically handled. This class is
built on top of native system libraries, libcurl on UNIX systems (Linux, macOS, BSD), WinInet on
Windows.
In addition to straightforward communication classes using blocking I/O, TSDuck also uses event dispatching on non-blocking I/O ("immediate" I/O on UNIX systems or "asynchronous" I/O on Windows). See chapter 4 for more details.
3.3.7. Shared libraries
The TSCore library contains classes to load shared libraries (.dll on Windows,
.so on Linux and BSD, .dylib on macOS) and lookup symbols inside them in a
portable way. These classes are typically used to load tsp plugins but can be
used in any application.
The class ts::SharedLibrary manipulates any type of shared library.
The subclass ts::ApplicationSharedLibrary searches a shared library using
TSDuck rules: if the file is not found "as it is", an optional prefix and a list
of directories are used. This is how, on Windows for instance, searching the
shared library named dektec will end up loading the file tsplugin_dektec.dll
in the same directory as the application executable file.
3.3.8. Smart-card interface
Applications which interact with smart-cards shall use the PC/SC interface. PC/SC is a standard interface which was originally developped for Windows but which is also available on Linux and macOS.
The TSDuck library does not embed or hide PC/SC but it provides a few utilities like transmitting an APDU and read the response in one single function or searching a smart-card with some characteristics in the ATR from all connected smart-cards.
All these utilities are grouped in the namespace ts::pcsc.
3.3.9. Windows specificities
The class ts::COM provides a portable and reliable way to make sure that
the Common Object Model (COM) is properly initialized and terminated on Windows
systems. This class is defined on all platforms but does nothing on non-Windows
systems. It is consequently safe to use it everywhere without tedious
conditional compilation directives.
Other classes manipulate Windows-specific objects and are not available on non-Windows systems.
The template class ts::ComPtr is the equivalent of a smart pointer for
COM objects. The reference count of a COM object is properly incremented and
decremented when the COM object is manipulated through a ts::ComPtr. The
COM object is automatically released when no more reference exists.
There is little advantage to develop an intrinsicly non-portable COM object class. However, in order to access tuner devices, TSDuck needed a few custom internal COM classes to interact with the DirectShow framework. These internal classes needed some COM support functions which are available to applications (just in case…)
3.4. MPEG features
3.4.1. Transport streams
The class ts::TSPacket defines a transport stream packet.
It is in fact a flat structure which occupies exactly 188 bytes in memory.
It is safe to use arrays or vectors of ts::TSPacket.
The packets are guaranteed to be contiguous in memory.
The class ts::TSPacket also adds many operations on the TS packet
to read or modify properties like the PID (type ts::PID),
the continuity counters or deeper structures like PCR, DTS or PTS.
The class ts::TransportStreamId contains the identification of an MPEG/DVB transport stream.
The class ts::Service contains all possible properties of a DVB service.
Not all properties need to be set at the same time.
Each property can be individually set, cleared or queried.
Transport stream files are implemented by classes ts::TSFileInput and ts::TSFileOutput.
They respectively read and write transport stream files
with specific features such as repeating the reading of a part of the file.
The subclass ts::TSFileInputBuffered provides additional, but limited,
capabilities to seek forward and backward on non-seekable files such as pipes.
The subclass ts::TSFileOutputResync adds resynchronization capabilities on continuity counters and PID’s.
The class ts::TSAnalyzer consumes all TS packets from a transport stream
and analyzes virtually everything from the stream.
This is the class which is used by the command tsanalyze and the plugin analyze
to collect the vast amount of information it reports.
The class ts::PCRAnalyzer is a useful tool to evaluate the bitrate of a transport stream.
It performs the analysis of the Program Clock Reference (PCR) which
are present in the transport stream in order to evaluate the bitrate of the stream.
If PCR are not found, the class can also use Decoding Time Stamps (DTS) to evaluate the bitrate.
This is less precise than PCR but can be used as a backup.
3.4.2. Audio, video and PES packets
The TSDuck library provides classes to manipulate PES packets and a few audio and video attributes. These features are limited to the analysis of a transport stream. There is no video or audio decoding features. Specialized libraries exist for this and are out of scope for TSDuck.
The class ts::PESPacket implements a PES packet and can manipulate its attributes, header and payload.
The class ts::PESDemux extracts PES packets from a transport stream.
It can also notify the application of the changes in audio or video attributes.
The abstract class ts::AbstractAudioVideoAttributes is the root of a hierarchy of classes
which contains attributes for audio or video streams.
Currently, specialized classes exist for MPEG-2 video, AVC/H.264, HEVC/H.265, VVC/H.266 video,
MPEG-2 audio and AC-3 audio.
The class ts::AVCParser performs the parsing of an AVC, HEVC, or VVC bitstream.
3.5. Signalization
The MPEG signalization is built from sections, tables and descriptors. All these concepts are implemented in the TSDuck library.
3.5.1. Binary, specialized and XML formats
Signalization objects, sections, tables and descriptors, can be manipulated in several formats: binary objects, specialized classes and XML.
Tables in JSON format are also supported through automatic XML-to-JSON translation.
The classes ts::Section, ts::BinaryTable and ts::Descriptor
implement binary forms of the signalization objects.
A binary table are made of a collection of sections. A binary table is valid when all binary sections are present. Each section contains its section number in the table and the total expected number of sections inside the table.
All sections and descriptors can be represented by the classes ts::Section and ts::Descriptor.
They simply contain the complete binary content of the object and can manipulate the various components.
An instance of ts::Section stores the table_id and manipulates the various components of the section header.
For long sections, the final CRC32 can be checked for consistency or recomputed after modification of the section content.
Tables can be stored in binary files.
The format of these files is quite simple.
They just contain raw binary sections, without any encapsulation.
Tables can also be stored in XML or JSON files.
The class ts::SectionFile reads and writes tables or section from files,
independently of the format, either a binary section file or an XML file.
Tables and descriptors can also be manipulated using specialized classes such as ts::PAT or ts::PMT
for tables and ts::ContentDescriptor or ts::ShortEventDescriptor for descriptors.
All specialized classes inherit from a common abstract root named ts::AbstractSignalization.
All descriptors inherit from the intemediate class ts::AbstractDescriptor.
All tables inherit from the intemediate class ts::AbstractTable.
Tables with long sections inherit from ts::AbstractLongTable.
Most tables and descriptors are implemented, from MPEG, DVB, ATSC, ISDB and a few private descriptors. Unimplemented descriptors shall be manipulated in binary form (or be implemented…)
Binary tables or descriptors are converted from or to specialized classes using serialize() and deserialize() methods.
The validity of a binary or specialized object can be checked using the isValid() method.
Sample deserialization code:
void someFunction(ts::DuckContext& duck, const ts::BinaryTable& table)
{
ts::PMT pmt;
if (table.isValid() && table.tableId() == ts::TID_PMT) {
pmt.deserialize(duck, table);
if (pmt.isValid()) {
processPMT(pmt);
}
}
}
The deserialization can also be done in the constructor. And the validity and table_id checking is done anyway in the deserialization. So, the previous code can be simplified as:
void someFunction(ts::DuckContext& duck, const ts::BinaryTable& table)
{
ts::PMT pmt(duck, table);
if (pmt.isValid()) {
processPMT(pmt);
}
}
Sample serialization:
ts::DuckContext duck;
ts::PMT pmt;
pmt.version = 12;
pmt.service_id = 0x1234;
// Declare one component, PID 0x345, carrying H.264/AVC video.
pmt.streams[0x345].stream_type = ts::ST_AVC_AUDIO;
ts::BinaryTable table;
pmt.serialize(duck, table);
Note that an instance of the class ts::DuckContext can store various information
about the way to interpret incorrect signalization or preferences.
Its default value is appropriate for a standard PSI/SI processing.
Each time the instance of ts::DuckContext is used, it accumulates information.
For instance, if it is used to deserialize an ATSC MGT table,
the information that the TS is an ATSC one is retained.
Later, if the same instance of ts::DuckContext is used to deserialize a descriptor
for which there is an ambiguity (the tag is used in two standards for instance),
the ATSC version of the descriptor will be used.
It is also possible to automatically define and load command line options to preset
the state of the instance of ts::DuckContext.
See section 3.5.3 for more details.
Finally, specialized classes for tables and descriptors can be converted
to and from XML using the methods toXML() and fromXML().
These methods are typically used by the class ts::SectionFile which represents
a file containing sections and tables in binary or XML format.
The class can be used to load a set of tables in XML format or to store table objects in XML format.
The class ts::SectionFile is the core of the tstabcomp utility, the tables compiler (or decompiler).
3.5.2. Demux and packetization
Signalization objects can be extracted from transport streams using the class ts::SectionDemux
and inserted back into transport streams using the class ts::Packetizer.
These two classes also have specialized subclasses.
An instance of ts::SectionDemux can extract sections or complete tables in binary form.
Tables with long sections are usually cycled. A given table with a given version number and a given table id extension is reported only once, after collecting all its sections. The same table will be reported again only when its version number changes.
On the contrary, short tables are all reported since they do not implement versioning.
It is also possible to use a ts::SectionDemux to be notified of all individual sections.
3.5.3. Application preferences contexts
The class ts::DuckContext carries various preferences about the standards or localizations.
Typically, each application has a given context.
Using tsp, each plugin has it own context.
The preferences which are carried by a context include the default standard (DVB, ATSC, ISDB), the default character sets in PSI/SI, the default private data specifier (for DVB private descriptors), the HF region (for terrestrial or satellite frequency mapping)
The ts::DuckContext class can automatically define command-line arguments
to explicitly specify preferences (options --atsc or --default-charset for instance).
Thus, the preferences are setup from the beginning.
But preferences are also accumulated all along the execution. For instance, as soon as an ATSC table is demuxed, the fact that the transport stream contains ATSC data is stored in the context. Later, when an MPEG table (a PMT for instance) contains an ambiguous descriptor tag which is used by DVB and ATSC, then the ATSC alternative will be used.
3.6. DVB SimulCrypt protocols
The communications inside a DVB SimulCrypt head-end is defined by the standard ETSI TS 103 197, "Head-end implementation of DVB SimulCrypt".
Most of these protocols use the same principles. They use binary TLV (Tag/Length/Value) messages, asynchronous communications, concepts of channels, streams, status and error messages.
The generic handling of these messages is implemented by classes in the namespace ts::tlv.
All TLV messages inherit from ts::tlv::Message.
Channel-level messages inherit from ts::tlv::ChannelMessage and
stream-level messages inherit from ts::tlv::StreamMessage.
The syntax of a given protocol is defined by subclassing ts::tlv::Protocol.
Currently, the TSDuck library implements the following protocols:
-
ECMG⇔SCS in namespace
ts::ecmgscs. -
EMMG/PDG⇔MUX in namespace
ts::emmgmux.
3.7. Conditional access systems
The class ts::CASMapper analyzes the signalization of a transport stream,
locates ECM and EMM stream and associates each of them with a CA_System_Id.
An instance of ts::CASMapper can then be queried for ECM, EMM streams or CAS vendors.
3.8. Other forms of demux
We have already mentioned the classes ts::SectionDemux and ts::PESDemux.
Other specialized forms of demux can be implemented.
The class ts::T2MIDemux demuxes T2-MI (DVB-T2 Modulator Interface) packets
and extracts encapsulated transport streams.
Similarly, the class ts::TeletextDemux extracts Teletext subtitles from TS packets.
Since all forms of demux share a number of properties, they all inherit from
a root abstract class named ts::AbstractDemux.
3.9. Digital TV tuners
The class ts::Tuner interfaces DVB/ATSC/ISDB tuner devices in a portable way.
This is quite a challenge since Linux and Windows use very different tuner frameworks.
Some very-specific features are available either only on Linux or Windows.
The abstract class ts::TunerArgs is the root of a hierarchy of classes containing tuning parameters.
Subclasses exist for DVB-S, DVB-T, DVB-C and ATSC.
ISDB-S and ISDB-T are currently unsupported.
The class ts::TSScanner reads a TS from a ts::Tuner until all scanning information is found,
typically until the PAT, NIT and SDT are received.
This is the basis for scanning a DTV network.
Note that tuner devices are supported on Linux and Windows only. On macOS, the above classes are defined but return "unimplemented" errors when used.
3.10. Interface to Dektec devices
TSDuck can manipulate ASI and (de)modulator devices from Dektec. The TSDuck library includes the DTAPI library, a proprietary C++ interface which is provided by Dektec. The DTAPI is not available in source form and not part of the TSDuck source repository. However, when TSDuck is built, the DTAPI is downloaded in binary from Dektec and included in the TSDuck library.
Such a packaging is authorized by the DTAPI license
(see the file OTHERS.txt in the TSDuck source repository or installation tree).
An application should not directly call the DTAPI.
In practice, this works on Linux but not on Windows.
So if you want portability, do not do this.
The reason is that the structure of Windows DLL’s is such that exported code
from a DLL must be compiled using specific attributes.
But the DTAPI, as provided by Dektec, was not compiled with these attributes.
So, when the DTAPI is included in tsduck.dll, the DTAPI can be called from
inside tsduck.dll but is not accessible from the application.
This is why accessing the DTAPI from the application must be done through some TSDuck proxy class.
The classes ts::DektecControl, ts::DektecInputPlugin and ts::DektecOutputPlugin
provide the features which are required by the utility tsdektec and the plugin dektec.
They can be used by third-party applications.
Note that Dektec devices are supported on Linux and Windows only. On macOS, the above classes are defined but return "unimplemented" errors when used.
4. The Reactor pattern and event dispatching
Some parts of TSDuck are based on a reactor pattern. The TSCore library provides an implementation of event dispatching based on a "Reactor" class. Because this topic is complex and not always well-understood, it deserves some specific explanations first.
4.1. Multithreading vs. event dispatching
Generally speaking, computing relies on three major types of resources: CPU, memory, and I/O. Most applications primarily need one type of resource which may become a bottleneck, a limiting factor which must be specifically addressed.
-
CPU-bound applications need more processing power than one CPU can provide. Ideally, they simultaneously use several CPU cores of the same machine, or even several machines.
-
Memory-bound applications need mode memory than the machine can provide. They need additional storage media or remotely-accessible memory, and swap between them.
-
I/O-bound applications constantly wait for input or output operations. They need to multiplex independent I/O operations at the highest possible rate.
An I/O-bound application (e.g. a server which processes simultaneous client connections) may use two possible types of implementation: multithreading and event dispatching. These are two distinct paradigms using different programming techniques. Usually, the choice of paradigm directly influences the architecture of the application.
4.1.1. Multithreading architecture
In a multithreading architecture, each sequential activity is linearly programmed in an independent thread of execution. A server typically contains one or two threads per client connection. If the client-server protocol is synchronous, with a strict request-response pattern, each client connection is implemented using one thread. It repeatedly reads a request, processes it, writes the response and waits for the next request. On the other hand, if the protocol is asynchronous, independent messages can flow in both directions at all time. In that case, a client connection is typically implemented using two threads: one thread processes incoming messages, the other thread generates outgoing messages.
The development of that architecture is simple. The processing is implemented using linear programming, reading and writing data when necessary. The core model is "blocking-I/O". Reading or writing a message suspends the execution of the thread during the I/O, until an incoming message is received or an outgoing message is fully sent.
On the other hand, for an I/O-bound application, a multithreading architecture is costly for the two other types of resources: CPU and memory. Constant context switching between threads is costly in terms of CPU and kernel resources. The impact on memory is even worse: each thread needs an independent execution stack, while spending most of the time doing nothing but waiting for the current I/O operation.
4.1.2. Event-dispatching architecture
In an event-dispatching application, there is only one thread of execution. That thread starts all I/O operations but never waits for them to complete. Instead, after starting one I/O for a client connection, for instance, it does some processing for another connection, until that client connection starts another I/O, etc. The core model is "immediate I/O" or "asynchronous I/O" (we will see the difference between the two later). The completion of each I/O is notified as an event. Processing that event means continuing the activity for the corresponding client connection, until it starts another I/O.
Because the driving principle of this architecture is "reacting" to events, the core feature of the application is named a "reactor design pattern". Object classes which are connected to the reactor and activated on reception of events are named "reactive classes".
I/O completion is one type of events but other types of events are possible: timers, used-defined events, process completions, message passing, etc.
The core reactor waits on events. Each time an event is received, an application-defined "event handler" is invoked. The event handler shall be quick. It shall not block or execute lengthy CPU-bound processing. It can only starts I/O, not wait for them to complete. Then, the event handler returns control to the reactor.
The benefit is obvious in terms of CPU and memory. There is only one thread with only one execution stack. Each client connection only requires its own contextual data. The kernel processes I/O only, not multiple threads and context switches.
However, because any benefit has a price, the programming technique is more complex. Each activity (e.g. a client connection) must be implemented as a state machine, using a specific data context and fragmented processing between I/O operations. Additionally, the sum of all CPU loads from the various activities shall not exceed the capability of one CPU.
The main practical limitation of reactors is the necessity to control absolutely all types of input/output so that no code suspends the execution of the reactor thread during an I/O. This means that third-party libraries which perform I/O under the hood cannot be used because calling a function of these libraries may block the reactor thread.
Some libraries, such as OpenSSL may have two modes: a simple one using linear programming and blocking I/O and a much more complex one where the library only performs data processing and lets the application perform the I/O. However, libraries such as libsrt and librist (which implement the SRT and RIST protocols, respectively) only have a linear API using blocking I/O. These libraries cannot be used in an application using an event dispatching architecture.
The second practical limitation of reactors is the absence of standard API and portability. The typical example of reactor-based or event-driven application is a graphical user interface (GUI). A GUI is the source of multiple events (mouse, keyboard, data sources) which can occur in any order, at any time. All GUI systems are based on the principle of event dispatching: Microsoft Windows, GTK, Qt, X-Window, etc. However, each environment defines its own API for event dispatching. They use different and incompatible APIs. To add to the confusion, other non-GUI event dispatching frameworks exist with different APIs, from the legacy Adaptive Communication Environment (ACE) to the modern libevent.
Because of the lack of a common unique, or universally accepted, standard API, it is not possible to develop portable applications. It is not possible to develop libraries which are based on a reactor API and smoothly integrate them in all event dispatching applications. As an example, libsrt proposes a "non-blocking I/O" mode but it is based on a specific libsrt API and it can be used only in applications which use libsrt as their reactor. When several libraries only accept their own specific model as event dispatcher, they cannot be used in the same application.
4.1.3. Multithreading vs. reactor comparison
The typical use cases of these two architectures are:
-
Multithreading: CPU-bound applications, with a limited number of simultaneous connections using synchronous protocols.
-
Reactor: I/O-bound applications, with a large number of simultaneous connections, or using asynchronous protocols.
| Multithreading | Reactor (event dispatching) | |
|---|---|---|
Pros |
|
|
Cons |
|
|
4.2. Blocking I/O vs. immediate I/O vs. asynchronous I/O
Traditional I/Os suspend the execution of the calling thread until the I/O operation completes. We call this "blocking I/O". In an event-dispatching architecture, no I/O should block the execution thread. However, there are several ways to perform I/O without blocking: immediate I/O and asynchronous I/O.
These methods are different and implemented on different operating systems: UNIX systems use immediate I/O and Windows uses asynchronous I/O. An application doesn’t have the choice between immediate I/O and asynchronous I/O. This is imposed by the operating system.
Because these two models of "non-blocking I/O" impose different data management strategies on the application, it is particularly challenging to define a portable Reactor API.
4.2.1. Immediate I/O (UNIX)
An "immediate I/O" is … immediate. Either it immediately completes or immediately fails but it never blocks. When an immediate I/O fails because it is not possible to immediately complete (e.g. because no incoming data is available), the process can be notified by the kernel when an I/O becomes possible. There are two main types of I/O events: "read-ready" and "write-ready", when a read or write operation becomes possible. Note that "ready" does not mean "guaranteed". On a read-ready event, for instance, the application shall retry the read operation. It will likely immediately succeed but it may also fail again (spurious read-ready event).
| The documentation of UNIX systems (Linux, macOS, BSD) uses the term "non-blocking" for immediate I/O. In a pure UNIX context, this is appropriate because those I/Os don’t block. However, in a wider context, there are different types of non-blocking I/O and the UNIX immediate I/O model is only one of them. In the TSDuck documentation, to avoid confusion between all types of non-blocking I/O, we use the term "immediate I/O" to refer to the UNIX model of non-blocking I/O. |
With immediate I/O, there is no specific constraint on the user data buffers. After an I/O call, whether it fails or succeeds, the user buffers are never used outside these I/O calls and the buffers can be disposed at any time. A device or socket can be closed at any time without impact on the data buffers.
To make portability more difficult, distinct flavors of UNIX systems use different incompatible system calls and kernel mechanisms to handle immediate I/O event notification.
-
Traditionally, all UNIX systems provide the
select()andpoll()system calls. However, they do not scale well with the number of events and they should now be completely avoided. -
macOS, FreeBSD, and other BSD systems use a mechanism named "kqueue" (kernel queue).
-
Linux uses a mechanism named "epoll" (event poll). There is also a more recent mechanism named "io_uring". It is more complex and particularly suited for extremely large servers. Currently, TSDuck uses epoll on Linux, not io_uring.
There are some limitations to the UNIX immediate I/O model:
-
The "immediate" success of an I/O is a flawed concept. It works well on network socket: is there any data available in the incoming buffer, or is there any free space in the outgoing buffer? Specifically, immediate input works only in a push model, e.g. when a remote peer sends data and these data are pushed into our reception buffer. However, it does not apply in a pull model, when the application requests data to be read from a disk. In that case, the "immediate I/O" blocks. The blocking time is limited thanks to the speed of disks but it may have an impact on heavily loaded reactors.
-
Some operations are lengthy by design. The typical example is a TCP
connect()operation. It requires to wait for a response from a remote server. This is much longer and more unpredictable than a disk I/O and it is impossible to make it blocking, pretending it is not. In practice, an immediate TCPconnect()is asynchronous. It always initially fails with a "try again later" status, just like any other immediate I/O. However, unlike other failed immediate I/Os, the actual connect operation continues in the background, in the kernel. When the connection is completed, the event "write-ready" is notified by epoll or kqueue. -
There are some cases of non-portable operations which make the development more complex. With disk I/O, we have seen that some I/O are blocking by design but sufficiently short to pretend that they are not, with limited impact when the number of events is not too high. On macOS and BSD systems, this is transparent. The same code can be used for network sockets and files, in the same reactor. On Linux, on the other hand, it fails. Regular files and a few other classes of devices cannot be set in non-blocking mode (in practice when the corresponding driver doesn’t support the "poll" operation). All I/O must be explicitly blocking. Files and sockets cannot be multiplexed in the same reactor, making the code more complex and error-prone, without any performance benefit.
4.2.2. Asynchronous I/O (Windows)
An "asynchronous I/O" always executes in the background. An I/O is first started using a classical read or write operation. In rare cases, it completes immediately (e.g. when data are already available from a kernel buffer). However, most of the time, the I/O is "pending", it runs in the background. Later, when the I/O completes, the completion event is reported to the application.
Microsoft Windows uses the name "overlapped I/O" instead of asynchronous I/O. The event synchronization mechanism is named I/O Completion Port, or IOCP in short.
Asynchronous I/O has an important impact on the data buffer management in the application. When a read operation starts, for instance, the address and size of the buffer are remembered in the kernel. This memory buffer is then written in the background, when data arrive as part of the read operation in progress. This means that the application must not reuse the memory of that buffer before the confirmation that the I/O completed, either successfully or with an error. If the application frees a data buffer during an asynchronous I/O, the following dangerous scenario becomes possible: the freed buffer is reused by some other part of the application and some precious data are stored there; then the asynchronous I/O continues and incoming data erase the reused buffer.
Therefore, an application must be very careful about data buffers. These buffers must never be freed or reused before the confirmation of the I/O completion. This can be challenging in error paths, when a device must be prematurely closed. The application shall not simply close the device and forget about it, as it would be with classical blocking I/O. The application shall explicitely cancel all pending I/Os, then wait for the cancelation to complete (i.e. the asynchronous I/O completes with a "canceled" status), then close the device, and sometimes wait for the completion of the close operation.
The Windows asynchronous I/O model has its limitations too:
-
The read and write operations on disk files are asynchronous, but not the operations which extend a file. So, when you write on a file and that operation needs additional disk space, the file management part is blocking and the data transfer part is not. Because the former is usually longer than the latter, this makes the "asynchronous" attribute is bit useless.
-
Some devices cannot be used in "overlapped" mode, such as anonymous pipes. This is a problem because an I/O on a pipe may block for hours if the peer does not read or write on the pipe. This is worse than Linux which forbids non-blocking I/O on disk files because these file I/O are always fast. They may slightly impact the performances of the application but not block it. Strangely enough, Windows named pipes can be used in overlapped mode. So, it is possible to use named pipes instead of anonyous pipes. However, it works only if the application creates the pipe. It doesn’t work if the pipe is already created, e.g. when used in standard input or standard output. This means that there is no way to guarantee that the standard input or output will always be usable for asynchronous I/O on Windows.
4.3. TSDuck hybrid architecture
So, which architecture is best-suited for TSDuck, multithreading or event dispatching? As usual, it depends…
TSDuck is an aggregate of heterogeneous blocks: libraries, executables, plugins, third-party libraries from various origins.
Originally, TSDuck used a strict multithreading architecture. There were several
reasons for this, mostly from the tsp requirements:
-
CPU load balancing between plugins. Some plugins can be CPU-intensive, such as encryption plugins. We need to leverage multi-core architectures. From the beginning, each plugin was executed in a dedicated thread.
-
Execution isolation between
tspplugins. This means avoiding that a plugin using blocking-I/O blocks other plugins. Note that this is "execution isolation" only, there is no memory or security isolation, as usual in multithreaded environments. -
Multiple third-party libraries which encapsulate blocking-I/O and cannot be used in an event dispatcher (SRT and RIST for instance).
-
Multiple operating systems support. Windows, Linux, macOS, BSD have very different ways of handling non-blocking I/Os in an efficient way. No common event dispatcher API was available.
-
TSDuck has always been designed as a flexible framework for debug and experimentation. Flexibility and extensibility prevail over raw performances. Using a multithreading architecture is more robust. An event dispatcher would fail in case of accidental blocking I/Os, whether they come from a third-party library or a bug in a plugin.
However, there are cases where using a strictly multithreading architecture is
less efficient or more difficult to write and maintain. Standalone tools such as
tsecmg are pure TCP servers, using asynchronous protocols and no third-party
library. Injection plugins (EMMs, MPE, SCTE-35) multiplex two sources of input:
the upstream transport stream and TCP or UDP incoming data. Their original
implementation included an additional thread to receive data from the network or
to poll data files. They would benefit from an event dispatching architecture.
Therefore, TSDuck is moving to a hybrid architecture, which mixes multithreading and event dispatching.
At high level, there are loosely coupled blocks, typically the plugins. The high-level architecture remains multithreaded. All plugins cannot be integrated into one single reactor. First, it would be too expensive to rewrite everything. More importantly, it would be too dangerous. One single plugin could block everything when using a third-party library with blocking I/O. One single CPU-bound plugin (e.g. encryption or decryption) could slow the entire system down.
At local level, inside a specific tool or plugin, when appropriate, a reactor can be used to multiplex and dispatch events inside one single thread.
This hybrid architecture is not uncommon and is sometimes named "multi-reactor pattern". Most of the time, it is used to balance a reactive application over multiple CPU cores: a thread executes on each CPU core and manages its own reactor and event dispatcher.
For this reason, the TSCore library has been extended in two ways:
-
The I/O classes were extended to support two modes: blocking and non-blocking.
-
A
Reactorclass and a set of associated reactive classes were added. They implement a core event dispatching mechanism with a portable API over Linux (epoll), macOS and BSD systems (kqueue), and Windows (IOCP). These classes also hide the differences between immediate I/O (UNIX) and asynchronous I/O (Windows).
| For convenience, and unless specified otherwise, we will use the term "non-blocking I/O" in a general sense of I/Os which don’t block the execution thread and are synchronized in a reactor. This indifferently includes the two principles of UNIX immediate I/O and Windows asynchronous I/O. |
4.4. Reactor and reactive classes
In the TSCore library, the core of the event dispatching features is class
ts::Reactor. An instance of this class is an event dispatcher. In the
case of the TSDuck hybrid architecture, there is one Reactor instance per
thread with an event dispatching architecture.
The typical scenario of event dispatching is the following:
-
Instantiate and open a
Reactor. -
Define and instantiate application classes which will handle events from the reactor. The event notification is done through "handler interface classes" that the application classes should implement.
-
Instantiate and initialize a few reactive objects (network, files, processes, etc). Make sure that the events will be notified to the application classes.
-
Enter the reactor method
processEventLoop(). -
The reactor calls event handlers in the various application classes, depending on events. An event handler typically starts other non-blocking operations which will trigger other handlers later.
-
If an event handler decides to terminate the event dispatching, it calls the reactor method
exitEventLoop(). After all event handlers return, the reactor methodprocessEventLoop()returns control to the application thread.
The Reactor shall be used from one single thread. Unless specified otherwise,
all methods shall be invoked from the thread of the reactor, where the event
loop is run. All handlers are invoked in the context of the thread which
invoked processEventLoop().
The Reactor and the associated reactive classes handle the following features:
-
Timers, either repeated or one-shot. A timer is identified by an id which is passed in the completion handler. The id can also be used to cancel a timer before it expires.
-
User-events. A user-event is created by the application and identified by an id. The application can trigger that event at any time. As an exception, a user-event can be triggered from an external thread. This is how synchronization can be achieved between a reactor and other threads.
-
Message queues. The class
ts::MessageQueuecan be used in aReactor. An event handler is called each time a message is available from the queue. BecauseMessageQueueis thread-safe by design, messages can be sent from external threads. -
Asynchronous I/O from the following types of devices:
-
UDP sockets.
-
TCP clients and servers.
-
Encrypted TLS clients and servers.
-
Binary files.
-
Pipes to and from processes which are created by
ts::ForkPipe.
-
-
I/O cancellation. It is possible the cancel an I/O in progress. The event handler for the completion of that I/O will be called with a "canceled" status.
-
Data presentation wrappers over asynchronous I/O channels:
-
Line-based text.
-
Tag-length-value (TLV) messages.
-
-
Process. An event handler is called when a process which was created by
ts::ForkPipeterminates. -
Web requests (HTTP, HTTPS, FTP), based on URL.
-
Worker delegation. When an event handler needs to run some lengthy computing, or call a library with blocking I/O, it cannot sequentially do that because it would block the reactor. Instead, it should delegate that treatment to a "worker" thread. A completion event handler will be called in the reactor when the delegated task completes. There is a pool of a maximum number of worker threads. A delegated task is either executed in an idle worker thread, executed in a newly created worker thread if none were idle, or queued for later execution if the maximum number of worker threads is reached.
Timers, user-events and process terminations are directly handled in class
Reactor.
The class Reactor encapsulates the various implementations of kernel event
dispatchers and proposes a portable interface. However, while it is possible to
unify the various types of immediate I/O (kqueue and epoll) in one single
interface, it is impossible to unify immediate I/O and asynchronous I/O into the
same interface. The way they shall be used, as well as the way the data buffers
are managed, are too different. Therefore, the class Reactor exposes
interfaces for both models. The application shall check the current I/O model
using the consteval static methods UseImmediateIO() and
UseAsynchronousIO() in class ReactorSupport and then adopt the correct
strategy.
In practice, because of this complexity, the I/O multiplexing features of class
Reactor are not used by applications. They are used in a few specialized
"reactive I/O" classes such as ts::ReactiveUDPSocket or
ts::ReactiveTCPConnection. These classes are implemented on top of
ts::Reactor and have fully portable and homogeneous interfaces where the
differences between immediate I/O and synchronous I/O are hidden.
The following UML-like class diagram summarizes the relationships between the
I/O classes, the Reactor class, and the reactive classes.
4.4.1. Standard I/O classes
On the left side of the diagram, the hierarchy of subclasses of
ts::Device contains all standard I/O classes.
The class name Device means a device with the potential of being used in
non-blocking mode. By default, instances of these classes work in blocking mode
and are suitable for use in linear programming. Applications can use them directly.
The three main types of I/O classes are binary files, pipes (to or from a forked process), and network sockets.
Files and pipes have a subclass for transport streams each.
TCP sockets, server and client connection, have a TLS subclass each, for encrypted servers and connections.
Files, pipes, and TCP connections expose the interface
ts::StreamInterface. This interface declares methods such as
writeStream(), readStream(), and endOfStream() which can be indifferently
used of files, pipes, and TCP connections.
Any class which exposes ts::StreamInterface can be used by
data-presentation classes such as ts::TextStream or ts::TLVStream,
to read and write text lines or TLV (tag, length, value) messages. Thus, it is
possible to indifferently exchange these data formats over files, pipes, TCP or
TLS connections.
Class ts::WebRequest handles all forms of Web requests, based on URL
(HTTP, HTTPS, FTP). This class is implemented using high-level system-specific
libraries: libcurl on UNIX systems (Linux, macOS, BSD), WinInet on Windows. Because these libraries don’t
expose their I/Os, class ts::WebRequest is not a subclass of
ts::Device. Therefore, unlike subclasses of ts::Device,
ts::WebRequest can be used in blocking mode only and an entirely
different class ts::ReactiveWebRequest is available for non-blocking
I/O.
4.4.2. Reactive I/O classes
On the right side of the Reactor class diagram, the hierarchy of subclasses of
ts::ReactiveBase contains all "reactive classes". Each instance of a
reactive class is attached to an instance of ts::Reactor. The same
instance of ts::Reactor dispatches events for all reactive classes which
use it.
An instance of a reactive class does not implement I/O. It uses an associated instance of a standard I/O class. The reactive class automatically sets the standard I/O class in non-blocking mode. The reactive class starts the I/O and uses the Reactor to synchronize on the I/O completion. When an I/O is completed, the reactive class calls a handler in a reactive interface.
The Reactor class diagram shows which level of reactive class uses which level
of standard I/O class. It is important to understand that there is only one
instance of a standard I/O class per instance of reactive class. For instance,
an instance of ts::ReactiveTCPConnection is associated to one instance
of ts::TCPConnection. The superclass ts::ReactiveStream uses the
superclass ts::StreamInterface and the superclass
ts::ReactiveDevice uses the superclass ts::Device, but all these
superclasses are implemented in the same two instances of
ts::ReactiveTCPConnection and ts::TCPConnection.
The reactive handler interfaces are implemented by the application-defined classes. When an application starts a non-blocking I/O, it specifies the handler which must be called upon completion. This is the address of an instance of an application class which implements the required handler interface class.
The following diagram summarizes all reactive handler interfaces, with their methods, and which reactive class calls them.
The application-defined classes need to implement the required handler interfaces, depending on which reactive class they use.
The interface ts::ReactorHandlerInterface is directly called by the
Reactor. The application may use it for timers, process completions, or user
events. Its methods handleReadReady(), handleWriteReady(), and
handleAsynchronousIO() are used only inside the reactive I/O classes and are
normally never used by an application.
Class ts::ReactiveWebRequest handles all forms of Web requests, based on
URL (HTTP, HTTPS, FTP). Like class ts::WebRequest, it is based on
high-level system-specific libraries: libcurl on UNIX systems (Linux, macOS, BSD), WinInet on
Windows. Because these libraries don’t expose their I/Os, class
ts::ReactiveWebRequest is not a subclass of
ts::ReactiveDevice. It is a direct subclass of ts::ReactiveBase
Similarly, ts::ReactiveWebRequest handles its own non-blocking I/Os
using specific modes of libcurl and WinInet. Unlike other types of reactive I/O
classes, it does not use a separate instance of ts::WebRequest.
4.4.3. Sample reactor-based application
The TSCore library contains class ts::ReactiveServer which implements a
general-purpose TCP server in a Reactor environment. It is suitable to
implement all kinds of servers, including SSL/TLS servers. An instance of
ts::ReactiveServer automatically creates application-defined session
objects when incoming clients connect and automatically deletes the session
object when they disconnect. The session objects are created by an instance of
ts::ReactiveServerFactoryInterface. The session object class must
implement ts::ReactiveServerSessionInterface.
The TSDuck source code repository contains a sample TCP server based on
ts::ReactiveServer in subdirectory
sample/sample-reactor-server.
This sample application is single-threaded and uses one single instance of
ts::Reactor to dispatch events in the TCP server and all client sessions.
By default, the server uses raw TCP connections. However, using the command line
option --tls, it creates a TLS server and encrypted connections. This
illustrates how ts::ReactiveServer can indifferently use an instance of
ts::ReactiveTCPServer or ts::ReactiveTLSServer.
The global instance of ts::ReactiveTCPServer (or its TLS counterpart)
uses an instance of ts::TCPServer. Similarly, each client connection uses
an instance of ts::ReactiveTCPConnections (or TLS), which uses an
instance of ts::TCPConnection.
The server handles text lines (it is a simplified version of an HTTP server).
It illustrates how ts::ReactiveTextStream can indifferently use an
instance of ts::ReactiveTCPConnection or
ts::ReactiveTLSConnection.
Each client session is implemented in an instance of the application-defined
class ClientConnection. Each session instance contains:
-
tcp_client: an instance ofts::TCPConnectionwhich handles the socket I/Os. -
react_client: an instance ofts::ReactiveTCPConnection(orts::ReactiveTCPConnection) which manages the synchronization of non-blocking I/Os and usestcp_clientfor I/Os. -
text_client: an instance ofts::ReactiveTextStreamwhich manages the data presentation (text lines) and usesreact_clientfor non-blocking I/Os.
The server management is implemented in an instance of the application-defined
class ServerControl. This class is used by ts::ReactiveServer for two
independent purposes:
-
Create instances of
ClientConnectionfor incoming client connections though the interfacets::ReactiveServerFactoryInterface. -
Handle global server events, such as server exit, though the interface
ts::ReactiveServerHandlerInterface.
This generic application structure is illustrated in the following general diagram which applies to all server applications.
5. Java and Python bindings
5.1. Overview
Starting with version 3.25, TSDuck includes Java and Python bindings to some high-level features.
Although subject to enhancements, these bindings will never aim at supporting the full TSDuck feature set since this would be too large. Only a small subset of TSDuck high-level features are targeted.
The Java classes are documented in the Java bindings reference section.
The Python classes are documented in the Python bindings reference section.
Currently, the TSDuck Java and Python bindings provide access to the features in the following table. Equivalences are provided between C++, Java, Python and command line tools.
The first three classes implement high-level features which have direct counterparts as command line tools. The others are support classes which are only required to use the high-level classes.
| Command | C++ class | Java class | Python class |
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
n/a |
|
|
|
n/a |
|
|
|
n/a |
|
|
|
n/a |
|
|
|
n/a |
|
|
|
n/a |
|
|
|
5.2. Support classes
5.2.1. TSDuck execution context
The DuckContext class is used to define and accumulate regional or operator preferences.
In the TSDuck C++ programming guide, it is referred to as TSDuck execution context.
Most of the time, using the default state of a new instance is sufficient.
5.2.2. Reporting classes
The reporting classes (ts::Report C++ class hierarchy) are used to report logs, errors and debug.
They are consistently used all over TSDuck and are required to use the high level features.
There is a large hierarchy of classes in the three languages which can be classified according to two sets of criteria:
-
Synchronous vs. asynchronous:
-
Synchronous report classes log messages in the same thread as the caller. They are usually not thread-safe.
-
Asynchronous report classes, on the other hand, can be used in a multi-threaded environment and the actual message logging (such as writing in a log file) is performed in a separate thread. As a consequence, an asynchronous report instance must be explicitly terminated. An asynchronous report class is required when using heavily multi-threaded classes such as
TSProcessororInputSwitcher.
-
-
Native vs. abstract:
-
Native classes are the C++ classes which are used in all the TSDuck command line tools. They are typically used to report to standard output, standard error, files or dropping the logs. They can be used from Java and Python directly but cannot be derived or customized. They are typically used when predefined error logging is sufficient.
-
Abstract classes are pure Java or Python base classes which are designed to be derived in applications. Such application-defined classes shall override the method
logMessageHandler(Java) orlog(Python) to intercept and process the message lines.
-
The asynchronous abstract classes can be useful to collect events, tables and
sections in XML, JSON or binary / hexadecimal form in Java or Python applications
when using TSProcessor or InputSwitcher.
Some of the sample Java and Python applications illustrate this mechanism.
| Category | C++ class | Java class | Python class |
|---|---|---|---|
Synchronous, native |
|
|
|
|
|
|
|
Asynchronous, native |
|
|
|
Synchronous, abstract |
|
|
|
Asynchronous, abstract |
|
|
|
5.2.3. Resource monitoring
The SystemMonitor class is available in all languages, C++, Java and Python.
It can be used at the top-level of an application to implement the --monitor option as found in tsp and tsswitch.
An instance of a thread-safe Report class is used to report monitoring messages.
5.2.4. Plugin events
For developers, TSDuck plugins can signal events which can be handled by the application.
Each event is signalled with a user-defined 32-bit event code.
An application can register event handlers in the ts::TSProcessor instance
(see the class ts::PluginEventHandlerRegistry, knowing that ts::TSProcessor
is a subclass of ts::PluginEventHandlerRegistry).
The event handler registration can include various selection criteria such as
event code value or originating plugin
(see the inner class ts::PluginEventHandlerRegistry::Criteria).
C++ developers who create their own plugins can signal any kind of event that they later handle in their application. This is illustrated in a C++ sample custom application. In this sample code, everything is customized in the application: the plugin, the event it signals, the associated event data, the application handling of the event.
Since developing a TSDuck plugin is only possible in C++, Java and Python developers have more limited options.
Some standard TSDuck plugins such as tables, psi or mpe provide the option --event-code.
Using this option, the plugins signal event using the specified event code for each data they handle
(sections or MPE datagrams depending on the plugin).
Java and Python applications can derive from class AbstractPluginEventHandler
to define and register their own event handlers.
Thus, binary sections or MPE datagrams can be handled directly from the plugin to the Java or Python application.
Some plugins are even dedicated to application developers and are useless on tsp command lines.
This is the case of the plugin memory (both an input and an output plugin).
This plugin, when used in a TSProcessor instance, performs direct transport stream input and output
from and to the application using memory buffers.
The memory buffers are signalled using plugin events.
The memory input plugin is an example of an application-defined event handler returning data to the plugin.
See this sample code
in the TSDuck source code tree.
5.3. Application/plugin communication in Java or Python
At high level, Java and Python applications can only run TSProcessor or InputSwitcher sessions,
just like a shell-script would do with commands tsp and tsswitch.
The communication from the Java and Python applications to the plugins is performed using plugin options. These options may contain file names or UDP ports which can be created by the application.
More effectively, most file contents can be provided directly on the command line, avoiding
the burden of creating temporary files. For instance, wherever an input XML file name is
expected, it is possible to use the XML content instead.
Any "XML file name" which starts with <?xml is considered as inline XML content.
Similarly, if an input "JSON file name" starts with { or [, it is considered as inline JSON content.
On reverse side, there is some limited form of communication from the plugins to the Java or Python application. There are basically two ways to handle plugin information in the application: the logging system and plugin events.
Using the logging system:
Some plugins support options such as --log-xml-line, --log-json-line or --log-hexa-line.
With these options, the extracted data (table, section, MPE datagram) are "displayed"
as one single line in the designated format on the logging system.
Using user-defined Java or Python asynchronous abstract reporting classes,
the application receives all logged lines and can filter and manipulate the data
which were extracted and logged by the plugins.
Using plugin events:
Some plugins support the option --event-code.
With this option, the extracted data are signalled by the plugin as an event.
Using and registering user-defined Java or Python plugin event handlers,
the application is directly notified of the data.
Which mechanism, logging system or plugin events, should be used depends on the application.
-
Logging system:
-
Pros:
-
The log lines are asynchronously processed in the context of the low-priority logging thread. Any lengthy processing in the Java or Python application does not hurt the dynamics of the plugins.
-
-
Cons:
-
If the application needs to process binary data, the additional serialization process in the log line adds some useless overhead.
-
Because the logging system is non-intrusive by design, log messages may be lost if there are more messages than the logging thread can process without making plugin threads wait. This can be mitigated using the synchronous log option in the
AbstractAsyncReportconsttructor.
-
-
-
Plugin events:
-
Pros:
-
The binary data are directly passed from the plugin to the application without any serialization, logging or multi-threading overhead.
-
-
Cons:
-
The application-defined event handlers execute in the context of the plugin thread. Any lengthy processing at this stage slows down the plugin.
-
-
The following sample applications can be used as a starting point:
| Communication type | Java | Python |
|---|---|---|
Logging (XML) |
||
Logging (JSON) |
||
Logging (bin/hexa) |
||
Plugin events (sections) |
||
Plugin events (MPE datagrams) |
||
Plugin events (input/output) |
5.4. Using TSDuck Java bindings
All TSDuck Java classes are defined in a package named io.tsduck.
A few examples are provided in the directory
sample/sample-java
in the TSDuck source code package.
5.4.1. Linux
The TSDuck Java bindings are installed with TSDuck in /usr/share/tsduck/java.
All classes are in a JAR file named tsduck.jar.
Simply add this JAR in the environment variable CLASSPATH to use TSDuck from any Java application:
$ export CLASSPATH="/usr/share/tsduck/java/tsduck.jar:$CLASSPATH"
5.4.2. macOS
This is similar to Linux, except that instead of /usr/share,
use /usr/local/share (Intel Macs) or /opt/homebrew/share (Apple Silicon Macs).
$ export CLASSPATH="/usr/local/share/tsduck/java/tsduck.jar:$CLASSPATH"
$ export CLASSPATH="/opt/homebrew/share/tsduck/java/tsduck.jar:$CLASSPATH"
5.4.3. Windows
On Windows, Java bindings are optional components of the TSDuck installer.
When they are selected for installation, they are installed in the TSDuck area and
the environment variable CLASSPATH is modified at system level
to include the JAR file of the TSDuck Java bindings.
Thus, any Java program can use TSDuck directly.
5.5. Using TSDuck Python bindings
All TSDuck bindings are defined in a module named tsduck.
All Python programs using TSDuck shall consequently start with:
import tsduck
A few examples are provided in the directory
sample/sample-python
in the TSDuck source code package.
5.5.1. Linux
The Python bindings are installed with TSDuck in /usr/share/tsduck/python.
Simply add this directory in the environment variable PYTHONPATH to use TSDuck from any Python application:
$ export PYTHONPATH="/usr/share/tsduck/python:$PYTHONPATH"
5.5.2. macOS
This is similar to Linux, except that instead of /usr/share,
use /usr/local/share (Intel Macs) or /opt/homebrew/share (Apple Silicon Macs).
$ export PYTHONPATH="/usr/local/share/tsduck/python:$PYTHONPATH"
$ export PYTHONPATH="/opt/homebrew/share/tsduck/python:$PYTHONPATH"
5.5.3. Windows
On Windows, Python bindings are optional components of the TSDuck installer.
When they are selected for installation, they are installed in the TSDuck area and
the environment variable PYTHONPATH is modified at system level
to include the root directory of the TSDuck Python bindings.
Thus, any Python program can use TSDuck directly.
5.5.4. Python prerequisites
The code was initially tested with Python 3.7 and higher. Python 2.x is not supported. Intermediate versions may work but without guarantee.
5.5.5. Implementation notes
There are usually two ways to call C/C++ from Python:
-
Using the predefined
ctypesPython module to call C functions, -
Implementating a full native Python module in C/C++.
The second option is usually more flexible and more generic. However, the generated binary depends on the version of Python. If such an option is used, the binary installation of TSDuck would require a specific version of Python (or a specific set of versions of it). But each system has it own requirements on Python and it is difficult for a product like TSDuck to impose a specific version of Python.
Consequently, the less flexible ctypes approach was chosen.
The TSDuck binary library contains C++ wrapper functions to some features of TSDuck and
these carefully crafted functions are directly called from Python code using ctypes,
regardless of the version of Python.
Note, however, that these C++ functions are hidden inside the Python bindings and
are invisible to the C++ application developer.
6. Developing a TSDuck plugin
6.1. Plugin development workflow
When some new kind of transport stream processing is needed, several solutions are possible:
-
First, check if an existing plugin or a combination of existing plugins can do the job.
-
Check if an existing plugin can be extended (by adding new options for instance).
-
As a last resort, develop a new plugin, which is relatively easy.
New plugins can be developed either as part of the TSDuck project or as independent third-party projects.
6.1.1. Developing independent third-party plugins
If you create your own third-party plugins (ie. if you are not a TSDuck maintainer), it is recommended to develop your plugins outside the TSDuck project.
Do not modify your own copy of the TSDuck project with your private plugins. This could create useless difficulties to upgrade with new versions of the project.
Consider developing your plugins in their own projects, outside TSDuck. You do not even need to get the full source code of TSDuck. It is sufficient to install the TSDuck development environment (see chapter 2).
An example of a third party plugin project is provided in the directory sample/sample-plugin.
6.1.2. Developing plugins for the TSDuck project
To develop a new plugin named foo, follow these steps:
-
Create a source file named
tsplugin_foo.cppin thetspluginssubdirectory. -
On UNIX systems (Linux, macOS, BSD), this new source file will be automatically recognized by the Makefile and the new plugin will be built.
-
On Windows systems, the plugin needs a "project file" for Visual Studio and MSBuild. This project file shall be referenced in the TSDuck "solution file".
The last step is automated using the Python script scripts/build-project-files.py.
This script explores the source files for all commands and plugins.
It automatically generates missing project files and references them in the solution file.
This script can be run on UNIX systems (Linux, macOS, BSD) or Windows systems.
On Windows, it can be easier to launch the PowerShell script scripts/build-project-files.ps1,
which simply calls the Python script.
6.2. Development guidelines
Don’t write a plugin from scratch.
Use an existing plugin as code base (beware however of the pitfalls of careless copy / paste).
The simplest code bases can be found in the plugins null (input), drop (output) , skip (basic packet processing),
nitscan (reading content of PSI/SI), svrename (modifying PSI/SI on the fly).
Always create plugins which perform simple and elementary processing. If your requirements can be divided into two independent processing, create two distinct plugins. The strength of TSDuck is the flexibility, that is to say the ability to combine elementary processing independently and in any order.
6.2.1. Class hierarchy
In the source file of the plugin, create a C++ class, derived from either
ts::InputPlugin, ts::OutputPlugin or ts::ProcessorPlugin.
If your plugin implements two capabilities (both input and output for instance),
implement the corresponding two classes in the same source file.
See the class diagram of ts::Plugin for a global view of the plugin classes.
Specialized plugins which manipulate exiting tables derive from ts::AbstractTablePlugin.
Examples of such plugins are pmt, pat, nit, etc.
The actual plugin subclasses focus on the modification of the target table
while the superclass automatically handles demuxing, remuxing and creation of non-existing tables.
Specialized descrambling plugins derive from ts::AbstractDescrambler.
This abstract class performs the generic functions of a descrambler:
service location, ECM collection, descrambling of elementary streams.
The concrete classes which derive from ts::AbstractDescrambler must perform CAS-specific operations:
ECM streams filtering, ECM deciphering, control words extraction.
Most of the time, these concrete classes must interact with a smartcard reader
containing a smartcard for the specific CAS.
6.2.2. Invoking tsp from a plugin, the ts::TSP callbacks
In its constructor, each plugin receives an associated ts::TSP object to communicate
with the tsp main executable.
This instance of ts::TSP is a protected field named tsp
which can be freely accessed by the code of the plugin.
A plugin shared library must exclusively use that tsp object for text display
and must never use std::cout, printf or the like.
The class ts::TSP is a subclass of ts::Report and supports all reporting methods
such as info(), verbose(), error(), debug(), etc.
When called in a multi-threaded context, the supplied tsp object is thread-safe and asynchronous
(the methods return to the caller without waiting for the message to be printed).
Note that the plugin instance is also a subclass of ts::Report and automatically redirects
all messages to its tsp field. Therefore, the code of the plugin can transparently use
its own methods info(), error(), etc. This is equivalent to calling its tsp.
6.2.3. Joint termination support
A plugin can decide to terminate tsp on its own (returning end of input, output error
or ts::ProcessorPlugin::TSP_END).
The termination is unconditional, regardless of the state of the other plugins.
Thus, if several plugins have termination conditions, tsp stops when the first plugin decides to terminate.
In other words, there is an "or" operator between the various termination conditions.
The idea behind joint termination is to terminate tsp when several plugins have jointly terminated their processing.
If several plugins have a "joint termination" condition,
tsp stops when the last plugin triggers the joint termination condition.
In other words, there is an "and" operator between the various joint termination conditions.
First, a plugin must decide to use joint termination.
This is usually done in method ts::Plugin::start(), using ts::TSP::useJointTermination(bool)
when the option --joint-termination is specified on the command line.
Then, when the plugin has completed its work, it reports this using ts::TSP::jointTerminate().
After invoking this method, any packet which is processed by the plugin may be ignored by tsp.
7. Developing a TSDuck extension
Applications or tsp plugins can be developed on their own.
But it is also possible to develop fully integrated extensions to TSDuck.
An extension not only adds new plugins and commands, it can also augment the features of standard TSDuck commands and plugins. An extension can also be packaged as a binary installer which can be deployed on top of an existing installation of TSDuck.
The possible features of a TSDuck extension are:
-
Handling third-party tables and descriptors. The new tables and descriptors can be manipulated in XML or JSON, analyzed and displayed with the standard TSDuck tools.
-
Handling third-party Conditional Access Systems, based on a range of CA_system_id values. The ECM’s, EMM’s and private parts of the CA_descriptor are correctly analyzed and displayed with the standard TSDuck tools.
-
Adding filtering capabilities based on specific or private conditions on sections for command
tstablesand plugintables. -
Additional plugins for
tsp. -
Additional command-line utilities.
A complete example
of a TSDuck extension is provided in the TSDuck source tree.
This example also provides scripts to build standard installers (.exe on Windows, .rpm and .deb on Linux).
The generated packages install the extension on top of a matching version of TSDuck.
7.1. Files in an extension
A TSDuck extension typically contains the following types of files:
-
Additional utilities. They are executable files without predefined naming. They are installed in the same directory as the TSDuck commands.
-
Additional
tspplugins. They are dynamic libraries namedtsplugin_XXX.so,.dylibor.dll. The plugins are loaded bytspwhen invoked by their namesXXX. -
Extension shared libraries named
tslibext_XXX.so,.dylibor.dll. All shareable libraries namedtslibext_XXXin the same directory as the TSDuck binaries or in the pathTSPLUGINS_PATHare automatically loaded when any TSDuck command is invoked (in fact any time the TSDuck librarytsduck.dllorlibtsduck.soor.dylibis used). Such libraries typically install hooks into the core of TSDuck to handle third-party signalization. -
XML files describing the XML models for third-party signalization (tables and descriptors). There is no mandatory naming template for those files but
tslibext_XXX.xmlis recommended. These XML files must be registered by the extension dynamic library (details below). -
Name files describing third party identifiers (table ids, descriptor tags, CA system id, stream types, etc.) These files are used by TSDuck to better identify the various entities. There is no mandatory naming template for those files but
tslibext_XXX.namesis recommended. These files must be registered by the extension dynamic library (details below).
7.2. The extension dynamic library
All shareable libraries named tslibext_XXX.so, .dylib or .dll are automatically loaded by any TSDuck command or plugin.
The initialization of the library is responsible for registering various hooks which implement the additional features.
7.2.1. Identification of the extension
This is an optional but recommended step.
One C++ module inside the tslibext_XXX library shall invoke the macro TS_REGISTER_EXTENSION as illustrated below:
TS_REGISTER_EXTENSION(u"foo", // extension name
u"Sample foo extension", // short description
{u"foot", u"foobar"}, // list of provided plugins for tsp
{u"footool", u"foocmd"}); // list of provided command-line tools
Using this declaration, the extension is identified and listed using the command tsversion --extensions.
Without the declaration, the extension is loaded and functional but it is not identified.
7.2.2. Providing an XML model file for additional tables and descriptors
To analyze input XML files containing tables, TSDuck uses an XML model to validate the syntax of the input XML file. There is a predefined large XML file which describes all supported tables and descriptors.
An extension may provide additional smaller XML files which describe the new tables or descriptors. See the sample extension for more details. The XML files shall be installed in the same directory as the rest of the extension (and TSDuck in general).
For each additional XML file, there must be one C++ module inside the tslibext_XXX
library which invokes the macro TS_REGISTER_XML_FILE as illustrated below:
TS_REGISTER_XML_FILE(u"tslibext_foo.xml");
7.2.3. Providing a names files for additional identifiers
The usage rules and conventions are identical to the XML file above.
The declaration macro for each names file is TS_REGISTER_NAMES_FILE as illustrated below:
TS_REGISTER_NAMES_FILE(u"tslibext_foo.names");
Here is an example, from the sample "foo" extension, which defines additional names for a table, a descriptor and a range of CA_system_id.
[TableId]
0xF0 = FOOT
[DescriptorId]
0xE8 = Foo
[CASystemId]
0xF001-0xF008 = FooCAS
7.2.4. Providing support for additional tables
If your environment defines a third-party table which is unsupported or unknown in TSDuck, you can implement it in your extension library.
First, define the C++ class implementing the table:
class FooTable : public ts::AbstractLongTable { ... };
In the implementation of the table, register hooks for the various features you support.
In this example, we register a C++ class for FooTable:
TS_REGISTER_TABLE(FooTable, // C++ class name
{0xF0}, // table id 0xF0
ts::Standards::NONE, // not defined in any standard
u"FOOT", // XML name is <FOOT>
FooTable::DisplaySection);
The last argument to TS_REGISTER_TABLE is a static method of the class
which displays the content of a section of this table type.
The XML model for the table is included in the XML file:
<?xml version="1.0" encoding="UTF-8"?>
<tsduck>
<_tables>
<FOOT version="uint5, default=0" current="bool, default=true" foo_id="uint16, required" name="string, optional">
<_any in="_descriptors"/>
</FOOT>
</_tables>
</tsduck>
for the following binary layout, using the same conventions as MPEG/DVB standards:
table_id 8 bits = 0xF0
section_syntax_indicator 1 bit = '1'
reserved 3 bits
section_length 12 bits
foo_id 16 bits
reserved 2 bits
version_number 5 bits
current_next_indicator 1 bit
section_number 8 bits
last_section_number 8 bits
name_length 8 bits
for(i=0;i<N;i++){
name_char 8 bits
}
reserved_future_use 4 bits
descriptors_length 12 bits
for (i=0;i<N;i++){
descriptor()
}
CRC_32
7.2.5. Providing support for additional descriptors
Similarly, it is possible to implement a third-party descriptor as follow:
class FooDescriptor : public ts::AbstractDescriptor { ... };
In the implementation of the descriptor, we register hooks for the various features.
Since this is a non-DVB descriptor with descriptor tag 0xE8, greater than 0x80,
we must set the private data specifier to zero in the ts::EDID ("extended descriptor id").
TS_REGISTER_DESCRIPTOR(FooDescriptor, // C++ class name
ts::EDID::Private(0xE8, 0), // "extended" descriptor id
u"foo_descriptor", // XML name is <foo_descriptor>
FooDescriptor::DisplayDescriptor);
The last argument to TS_REGISTER_DESCRIPTOR is a static method of the class
which displays the content of a descriptor.
The XML model for the descriptor is included in the XML file:
<?xml version="1.0" encoding="UTF-8"?>
<tsduck>
<_descriptors>
<foo_descriptor name="string, required"/>
</_descriptors>
</tsduck>
for the following binary layout:
descriptor_tag 8 bits = 0xE8
descriptor_length 8 bits
for(i=0;i<N;i++) {
name_char 8 bits
}
7.2.6. Implementing advanced section filtering capabilities
The command tstables (and its plugin counterpart tables) can process vast amounts of tables.
To extract specific tables or sections, the command provides filtering options such as --pid, --tid or --tid-ext.
For specific sections, it is possible to define additional filtering options to the tstables command.
The extension library shall provide a C++ class implementing ts::TablesLoggerFilterInterface.
The sample foo extension provide an option --foo-id
which selects instances of FooTable containing specific values for some foo_id field.
class FooFilter: public ts::TablesLoggerFilterInterface { ... };
See the documentation of ts::TablesLoggerFilterInterface for more details.
In the implementation of the class, we register it as a section filter for tstables:
TS_REGISTER_SECTION_FILTER(FooFilter);
7.2.7. Providing support for additional Conditional Access Systems
If you work with a specific Conditional Access System, you probably manipulate confidential information that cannot be published in an open-source tool such as TSDuck. The solution is to develop a private closed-source extension.
In the extension library, you may register functions to display the structure
of the ECM’s, EMM’s or private part of the CA_descriptor.
The registration is based on a range of CA_system_id (here the constants CASID_FOO_MIN and CASID_FOO_MAX).
// Display a FooCAS ECM on the output stream.
// Compatible with ts::DisplaySectionFunction profile.
void DisplayFooCASECM(ts::TablesDisplay& display, const ts::Section& section, int indent);
// Display a FooCAS EMM on the output stream.
// Compatible with ts::DisplaySectionFunction profile.
void DisplayFooCASEMM(ts::TablesDisplay& display, const ts::Section& section, int indent);
// Display the payload of a FooCAS ECM on the output stream as a one-line "log" message.
// Compatible with ts::LogSectionFunction profile.
ts::UString LogFooCASECM(const ts::Section& section, size_t max_bytes);
// Display the payload of a FooCAS EMM on the output stream as a one-line "log" message.
// Compatible with ts::LogSectionFunction profile.
ts::UString LogFooCASEMM(const ts::Section& section, size_t max_bytes);
// Display the private part of a FooCAS CA_descriptor on the output stream.
// Compatible with ts::DisplayCADescriptorFunction profile.
void DisplayFooCASCADescriptor(ts::TablesDisplay& display, const uint8_t* data, size_t size, int indent, ts::TID tid);
See the documentation for ts::DisplaySectionFunction, ts::LogSectionFunction
and ts::DisplayCADescriptorFunction.
To register the display handlers in TSDuck:
TS_REGISTER_SECTION({ts::TID_ECM_80, ts::TID_ECM_81},
ts::Standards::NONE, // not defined in any standard
DisplayFooCASECM, // display function
LogFooCASECM, // one-line log function
{}, // no predefined PID
CASID_FOO_MIN, // range of CA_system_id
CASID_FOO_MAX);
TS_REGISTER_SECTION(ts::Range<ts::TID>(ts::TID_EMM_FIRST, ts::TID_EMM_LAST),
ts::Standards::NONE, // not defined in any standard
DisplayFooCASEMM, // display function
LogFooCASEMM, // one-line log function
{}, // no predefined PID
CASID_FOO_MIN, // range of CA_system_id
CASID_FOO_MAX);
TS_REGISTER_CA_DESCRIPTOR(DisplayFooCASCADescriptor, CASID_FOO_MIN, CASID_FOO_MAX);
7.3. Building cross-platform binary installers for an extension
See the sample foo extension
in the TSDuck source tree.
Scripts are provided to build .exe installers on Windows, .rpm and .deb packages on Linux.
| To avoid unexpected issues, an extension is only compatible with the version of TSDuck it was compiled with. When you install a new version of TSDuck, make sure to rebuild your extension with the development environment of this specific version of TSDuck. Then, install the new version of the extension on top of the same new version of TSDuck. |
Appendix A: PSI/SI Signalization Reference
All signalization tables and descriptors which are supported by TSDuck are documented in the TSDuck user guide, appendix D "PSI/SI XML Reference Model".
A.1. PSI/SI tables
The table below summarize all available PSI/SI tables in TSDuck and the reference of the standard which specifies them.
| XML name | C++ class | Defining document |
|---|---|---|
|
|
ATSC A/81, 9.9.2 |
|
|
ATSC A/81, 9.9.3 |
|
|
ETSI TS 101 812, 10.4.6 |
|
|
ARIB STD-B10, Part 2, 5.2.16 |
|
|
ATSC A/65, 6.5 |
|
|
ETSI EN 300 468, 5.2.2 |
|
|
ARIB STD-B10, Part 2, 5.2.13 |
|
|
ANSI/SCTE 18, 5 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.4.4.6 |
|
|
ARIB STD-B21, 12.2.2.2 |
|
|
ETSI TS 102 323, 12.2 |
|
|
ETSI TS 102 323, 7.3.1.4 |
|
|
ATSC A/65, 6.3.2 |
|
|
ATSC A/65, 6.8 |
|
|
ATSC A/65, 6.7 |
|
|
ARIB STD-B16, 4.3 |
|
|
ATSC A/90, 11.3.1 |
|
|
ETSI EN 303 560, 5.3.2.3 |
|
|
ETSI EN 300 468, 7.1.1 |
|
|
ARIB STD-B16, 4.4 |
|
|
ISO/IEC 13818-6, 9.2.2 and 7.2.2 |
|
|
ISO/IEC 13818-6, 9.2.2 and 9.2.7 |
|
|
ISO/IEC 13818-6, 9.2.2, 7.3.2, 7.3.6 |
|
|
ATSC A/90, 12.2 |
|
|
ETSI EN 300 468, 5.2.4 |
|
|
ARIB STD-B10, Part 3, 5.1.2 |
|
|
ATSC A/65, 6.6 |
|
|
ETSI EN 301 192, 8.4.3 |
|
|
ARIB STD-B10, Part 3, 5.1.3 |
|
|
ARIB STD-B10, Part 2, 5.2.15 |
|
|
ARIB STD-B10, Part 3, 5.1.1 |
|
|
ATSC A/90, 11.7 |
|
|
ATSC A/65, 6.2 |
|
|
ETSI EN 301 192, 9.9 |
|
|
ETSI TS 102 772, 5.2 |
|
|
ARIB STD-B10, Part 2, 5.2.14 |
|
|
ETSI EN 300 468, 5.2.1 |
|
|
ATSC A/90, 12.3 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.4.4.3 |
|
|
ARIB STD-B10, Part 2, 5.2.12 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.4.4.8 |
|
|
ETSI TS 102 323, 10.4.2 |
|
|
ETSI TS 102 323, 5.2.2 |
|
|
ATSC A/65, 6.4 |
|
|
ETSI EN 300 468, 5.2.7 |
|
|
ETSI EN 300 468, 5.2.11 |
|
|
ETSI EN 300 468, 5.2.3 |
|
|
ARIB STD-B21, 12.2.1.1 |
|
|
ETSI EN 300 468, 7.1.2 |
|
|
Astra LCN Technical Specification, 2.1 |
|
|
ANSI/SCTE 35, 9.2 |
|
|
ATSC A/65, 6.1 |
|
|
ATSC A/81, 9.9.1 |
|
|
ETSI EN 300 468, 5.2.5 |
|
|
ETSI EN 300 468, 5.2.6 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.4.4.12 |
|
|
ATSC A/65, 6.3.1 |
|
|
ETSI TS 102 006, 9.4.1 |
A.2. PSI/SI descriptors
The table below summarize all available PSI/SI desciptors in TSDuck and the reference of the standard which specifies them.
| XML name | C++ class | Defining document |
|---|---|---|
|
|
ETSI EN 300 468, H.2.1 |
|
|
ETSI EN 300 468, 6.2.1 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.99 |
|
|
ETSI EN 300 468, 6.2.2 |
|
|
ETSI EN 300 468, 6.2.3 |
|
|
ETSI TS 102 809, 5.3.5.3 |
|
|
ETSI TS 102 809, 5.3.5.6.2 |
|
|
ETSI TS 101 812, 10.7.4.1 |
|
|
ETSI TS 102 809, 5.3.5.4 |
|
|
ETSI TS 102 809, 5.3.5.1 |
|
|
ETSI TS 102 809, 5.3.10.1 |
|
|
ETSI TS 102 809, 5.3.5.5 |
|
|
ARIB STD-B10, Part 2, 6.2.55 |
|
|
ISO/IEC 13818-6 (DSM-CC), 11.4.2 |
|
|
Astra LCN Technical Specification, 2.3.2 |
|
|
Astra LCN Technical Specification, 2.3.1 |
|
|
Astra LCN Technical Specification, 2.3.3 |
|
|
ATSC A/52, A.4.3 |
|
|
ATSC A/71, 6 |
|
|
ATSC A/90, 7.2.3.5.3 |
|
|
ATSC A/90, 11.5 |
|
|
ATSC A/90, 12.2.3 |
|
|
ATSC A/52, G.3.5 |
|
|
ATSC A/90, 7.2.3.5.4 |
|
|
ATSC A/90, 7.2.3.5.2 |
|
|
ATSC A/90, 12.2.4 |
|
|
ATSC A/71, 7 |
|
|
ATSC A/90, 11.6 |
|
|
ATSC A/53, Part 3, 5.8.2 |
|
|
ATSC A/65, 6.9.8 |
|
|
ATSC A/65, 6.9.6 |
|
|
ARIB STD-B10, Part 2, 6.2.26 |
|
|
ETSI EN 300 468, 6.4.1 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.4 |
|
|
Free TV Australia Operational Practice OP-41, 2.2 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.74 and ISO/IEC 23002-3 |
|
|
|
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.66 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.64 |
|
|
AVS T/AI 109.7 |
|
|
AVS T/AI 109.7 |
|
|
AVS T/AI 109.6, 9.3.2 |
|
|
ARIB STD-B10, Part 3, 5.2.1 |
|
|
ARIB STD-B10, Part 2, 6.2.39 |
|
|
ETSI EN 300 468, 6.2.4 |
|
|
ARIB STD-B10, Part 2, 6.2.36 |
|
|
ETSI EN 300 468, 6.4.6.4 |
|
|
ETSI EN 300 468, 6.4.6.1 |
|
|
ARIB STD-B25, Part 1, 4.7.2 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.16 |
|
|
ARIB STD-B25, Part 1, 4.7.1 |
|
|
ETSI EN 300 468, 6.2.5 |
|
|
ARIB STD-B25, Part 1, 4.7.3 |
|
|
ETSI EN 300 468, 6.2.13.1 |
|
|
ATSC A/65, 6.9.2 |
|
|
ARIB STD-B10, Part 2, 6.2.46 |
|
|
ISO/IEC 13818-6 (DSM-CC), 11.4.1 |
|
|
ETSI EN 300 468, 6.2.6 |
|
|
ETSI EN 300 468, 6.2.7 |
|
|
ETSI EN 300 468, 6.4.1 |
|
|
ETSI EN 300 468, 6.2.8 |
|
|
ATSC A/65, 6.9.7 |
|
|
ARIB STD-B25, Part 2, 2.3.2.6.4 |
|
|
ATSC A/65, 6.9.3 |
|
|
ARIB STD-B10, Part 2, 6.2.45 |
|
|
ETSI EN 300 468, 6.2.9 |
|
|
ETSI TS 102 323, 12.1 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.56 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.24 |
|
|
ETSI EN 300 468, 6.2.10 |
|
|
ETSI EN 300 468, 6.4.2 |
|
|
ETSI EN 300 468, 6.4.3 |
|
|
ETSI TS 102 825-9, 4.1.5 and ETSI TS 102 825-4, 5.4.5 |
|
|
ANSI/SCTE 35, 8.2 |
|
|
T/UWA 005-2.1 |
|
|
ETSI EN 300 468, 6.2.11 |
|
|
ETSI EN 300 468, 6.2.12 |
|
|
ARIB STD-B10, Part 2, 6.2.20 |
|
|
ARIB STD-B10, Part 2, 6.2.28 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.10 |
|
|
ATSC A/65, 6.9.11 |
|
|
ATSC A/65, 6.9.10 |
|
|
ETSI TS 102 323, 6.3.3 and, 5.2.2 for interpretation |
|
|
ISO/IEC 13818-6 (DSM-CC), 11.4.3 |
|
|
ARIB STD-B10, Part 2, 6.2.23 |
|
|
ETSI TS 101 812, 10.8.3.3 |
|
|
ARIB STD-B21, 12.2.1.1 |
|
|
ETSI TS 102 809 V1.3.1 (2017-06), B.2.2.4.2 |
|
|
ETSI EN 301 192 V1.7.1 (2021-08), 10.2.11 |
|
|
ETSI TS 102 809, B.2.3.4 |
|
|
ETSI EN 301 192 V1.7.1 (2021-08), 10.2.6 |
|
|
ETSI EN 301 192 V1.7.1 (2021-08), 10.2.8 |
|
|
ETSI EN 301 192 V1.7.1 (2021-08), 10.2.9 |
|
|
ETSI EN 301 192 V1.7.1 (2021-08), 10.2.4 |
|
|
ETSI TS 102 727 V1.1.1 (2010-01), B.2.2.4.1 |
|
|
ETSI EN 301 192 V1.7.1 (2021-08), 10.2.7 |
|
|
ETSI EN 301 192 V1.7.1 (2021-08), 10.2.5 |
|
|
ETSI EN 301 192 V1.7.1 (2021-08), 10.2.3 |
|
|
ETSI TS 102 006 V1.4.1 (2015-06), 8.2.1 |
|
|
ETSI TS 102 006, 9.6.2.1 |
|
|
ETSI EN 301 192 V1.7.1 (2021-08), 10.2.2 |
|
|
ETSI EN 300 468, 6.2.14 |
|
|
The D-Book 7 Part A (DTG), 8.5.3.20 |
|
|
The D-Book 7 Part A (DTG), 8.5.3.23 |
|
|
The D-Book 7 Part A (DTG), 8.5.3.6 |
|
|
The D-Book 7 Part A (DTG), 8.5.3.8 |
|
|
The D-Book 7 Part A (DTG), 8.5.3.7 |
|
|
The D-Book 7 Part A (DTG), 8.5.3.9 |
|
|
The D-Book 7 Part A (DTG), 8.5.3.10 |
|
|
ITU J.1041, 7.2.1 |
|
|
ETSI EN 300 468, G.2.1 |
|
|
ETSI EN 300 468, G.3.1 |
|
|
ETSI EN 300 468, L.1 |
|
|
ETSI EN 300 468, annex G |
|
|
ETSI EN 300 468, D.3 |
|
|
ETSI EN 300 468, D.7 |
|
|
ETSI EN 300 468, D.5 |
|
|
ETSI TS 101 812, 10.10.3 |
|
|
ETSI TS 101 812, 10.10.1 |
|
|
ETSI TS 101 812, 10.10.2 |
|
|
ETSI TS 101 812, 10.9.1 |
|
|
ETSI TS 101 812, 10.9.2 |
|
|
ETSI EN 300 468, 6.2.40 |
|
|
ETSI EN 300 468, 6.2.45 |
|
|
EACEM Technical Report Number TR-030, 9.2.11.2 |
|
|
EACEM Technical Report Number TR-030, 9.2.11.2 |
|
|
EACEM Technical Report Number TR-030, 9.2.11.2 |
|
|
EACEM Technical Report Number TR-030, 9.2.11.2 |
|
|
ANSI/SCTE 18, 5.1.3 |
|
|
ANSI/SCTE 18, 5.1.1 |
|
|
ANSI/SCTE 18, 5.1.2 |
|
|
ANSI/SCTE 164, 5.0 |
|
|
ETSI EN 301 192, 9.7 |
|
|
ARIB STD-B10, Part 2, 6.2.24 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.135 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.133 |
|
|
ARIB STD-B10, Part 2, 6.2.34 |
|
|
ARIB STD-B10, Part 2, 6.2.43 |
|
|
ATSC A/65, 6.9.4 |
|
|
ETSI EN 300 468, 6.2.15 |
|
|
ETSI TS 102 809, 5.3.5.7 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.46 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.44 |
|
|
ETSI EN 300 468, 6.2.17 |
|
|
ETSI EN 300 468, 6.2.18 |
|
|
ATSC A/65, 6.9.13 |
|
|
ETSI TS 102 809, 5.3.5.8 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.104 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.102 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.100 |
|
|
ISO/IEC 13818-1, 2.6.138 |
|
|
ISO/IEC 13818-1 clasue 2.6.122 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.97 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.95 |
|
|
ARIB STD-B10, Part 2, 6.2.22 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.6 |
|
|
ARIB STD-B10, Part 2, 6.2.58 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.34 |
|
|
ETSI EN 300 468, 6.4.7 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.40 |
|
|
ETSI TS 101 812, 10.8.2 |
|
|
ETSI EN 301 192, 8.4.5.15 |
|
|
ETSI EN 301 192, 8.4.5.2 |
|
|
ETSI EN 301 192, 8.4.5.3 |
|
|
ETSI EN 301 192, 8.4.5.14 |
|
|
ARIB STD-B10, Part 2, 6.2.54 |
|
|
JCTEA STD-003, 6.2 |
|
|
ARIB STD-B61, Volume 2, 4.6.1 |
|
|
JCTEA STD-003, 6.2 J2 |
|
|
ARIB STD-B63, 13.1.2.1 |
|
|
ARIB STD-B10, Part 2, 6.2.37 |
|
|
ARIB STD-B10, Part 2, 6.2.41 |
|
|
ARIB STD-B61, Volume 2, 4.4.7.1 |
|
|
ARIB STD-B10, Part 2, 6.2.29 |
|
|
ARIB STD-B10, Part 2, 6.2.40 |
|
|
ARIB STD-B21, Part 2, 9.1.8.3 |
|
|
ARIB STD-B10, Part 1, 6.2, Figure 6.2-68 |
|
|
ARIB STD-B10, Part 2, 6.2.27 |
|
|
ARIB STD-B10, Part 2, 6.2.31 |
|
|
JCTEA STD-003, 6.2 J3 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.18 |
|
|
ETSI EN 301 192, 8.4.5.16 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.80 |
|
|
ISO/IEC 13818-1 2.6.127, ITU-T H.222.0 |
|
|
ISO/IEC 13818-1 (Amd.1) 2.6.137, ITU-T H.222.0 |
|
|
ISO/IEC 13818-1 (Amd.1) 2.6.137, ITU-T H.222.0 |
|
|
ETSI EN 300 468, 6.2.19 |
|
|
ETSI EN 300 468, 6.2.19 |
|
|
ETSI EN 300 468, 6.2.20 |
|
|
ARIB STD-B10, Part 2, 6.2.44 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.54 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.50 |
|
|
ARIB STD-B10, Part 3, 5.2.6 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.26 |
|
|
ISO/IEC 13818-1 (Amd.1) 2.6.141 |
|
|
ETSI EN 300 468, 6.4.7 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.60 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.58 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.62 |
|
|
ETSI EN 300 468, 6.2.21 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.68 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.84 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.38 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.72 |
|
|
ITU-T H.222.0, 2.6.70 and ISO/IEC 14496-17 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.36 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.118 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.108 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.106 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.116 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.114 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.110 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.112 |
|
|
ETSI EN 300 468, 6.2.22 |
|
|
ETSI EN 300 468, 6.2.23 |
|
|
ETSI EN 300 468, 6.2.24 |
|
|
ETSI EN 300 468, 6.2.25 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.52 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.22 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.48 and ISO/IEC 14496-1, 7.4.2.5 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.78 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.82 |
|
|
ETSI EN 300 468, 6.4.9 |
|
|
ARIB STD-B21, 12.2.1.1 |
|
|
ETSI EN 300 468, 6.2.27 |
|
|
ARIB STD-B10, Part 3, 5.2.3 |
|
|
NorDig Unified Requirements ver. 3.1.1, 12.2.9.2 |
|
|
NorDig Unified Requirements ver. 3.1.1, 12.2.9.3 |
|
|
ISO/IEC 13818-6, 8.1.5 |
|
|
ISO/IEC 13818-6, 8.1.1 |
|
|
ETSI EN 300 468, 6.2.26 |
|
|
ETSI EN 300 468, 6.2.28 |
|
|
ARIB STD-B10, Part 2, 6.2.32 |
|
|
ETSI EN 300 468, 7.2.1 |
|
|
ARIB STD-B21, 9.1.8.3 (3) |
|
|
ETSI EN 300 468, 6.2.30 |
|
|
ETSI TS 101 812, 10.8.3.2 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.28 |
|
|
ETSI EN 300 468, 6.2.31 |
|
|
ETSI TS 102 809, 9.3.3 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.119 and ISO/ISC 23001-10 |
|
|
ETSI TS 102 323, 5.3.5 |
|
|
ETSI TS 102 323, 5.3.6 |
|
|
ATSC A/65, 6.9.12 |
|
|
ARIB STD-B10, Part 3, 5.2.2 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.8 |
|
|
ETSI TS 102 323, 10.3 |
|
|
ETSI TS 102 323, 5.3.7 |
|
|
ETSI EN 300 468, 6.2.13.3 |
|
|
ETSI EN 300 468, 6.4.6.5 |
|
|
ETSI EN 300 468, 6.4.6.5.3 |
|
|
ETSI EN 300 468, 6.2.13.2 |
|
|
ETSI TS 102 006, 9.5.2.9 |
|
|
ETSI EN 300 468, 6.2.32 |
|
|
ARIB STD-B10, Part 2, 6.2.33 |
|
|
ETSI EN 300 468, 6.2.34 |
|
|
ETSI EN 300 468, 6.2.33 |
|
|
ARIB STD-B10, Part 2, 6.2.49 |
|
|
ETSI TS 102 809, 6.2.1 |
|
|
ETSI EN 300 468, 6.2.35 |
|
|
ATSC A/65, 6.9.5 |
|
|
ETSI EN 300 468, 6.2.34 |
|
|
ETSI EN 300 468, 6.4.18 |
|
|
ETSI EN 300 468, 6.4.9 |
|
|
ETSI EN 300 468, 6.4.6.2 |
|
|
ETSI EN 300 468, 6.2.37 |
|
|
ARIB STD-B10, Part 3, 5.2.4 |
|
|
ETSI EN 300 468, 6.2.38 |
|
|
ARIB STD-B10, Part 2, 6.2.35 |
|
|
ARIB STD-B10, Part 2, 6.2.38 |
|
|
ETSI TS 102 809, 5.3.8 |
|
|
ETSI TS 102 809, 5.3.7 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.42 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.30 |
|
|
SMPTE ST 2038, 4.1.2 |
|
|
ANSI/SCTE 35, 10.3.5 |
|
|
ANSI/SCTE 35, 10.3.1 |
|
|
ANSI/SCTE 35, 10.3.2 |
|
|
ANSI/SCTE 35, 10.3.3 |
|
|
ANSI/SCTE 35, 10.3.4 |
|
|
ETSI TS 102 006, 9.5.2.14 |
|
|
ETSI TS 102 006, 9.5.2.11 |
|
|
ETSI TS 102 006, 9.5.2.7 |
|
|
ETSI TS 102 006, 9.5.2.12 |
|
|
ETSI TS 102 006, 9.5.2.8 |
|
|
ETSI TS 102 006, 9.5.2.15 |
|
|
ARIB STD-B10, Part 3, 5.2.5 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.32 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.86 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.88 |
|
|
ISO/IEC 13818-6, 8.3 |
|
|
ETSI EN 300 468, 6.2.39 |
|
|
ISO/IEC 13818-6, 8.2 |
|
|
ETSI EN 300 468, 6.2.41 |
|
|
ETSI EN 300 468, 6.4.11 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.76 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.20 |
|
|
ARIB STD-B10, Part 2, 6.2.21 |
|
|
ETSI EN 300 468, 6.4.6.3 |
|
|
ETSI EN 300 468, 6.4.14 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.12 |
|
|
ETSI EN 301 192, 8.4.5.8 |
|
|
ETSI EN 301 192, 8.4.5.9 |
|
|
ETSI EN 301 192, 8.4.5.10 |
|
|
ETSI EN 301 192, 8.4.5.11 |
|
|
ETSI EN 301 192, 8.4.5.12 |
|
|
ETSI EN 301 192, 8.4.5.13 |
|
|
ETSI EN 301 192, 8.4.5.6 |
|
|
ETSI EN 301 192, 8.4.5.7 |
|
|
ETSI EN 300 468, 6.4.12 |
|
|
ETSI EN 300 468, 6.4.13 |
|
|
ETSI EN 301 192, 8.4.5.4 |
|
|
ETSI EN 301 192, 8.4.5.5 |
|
|
ETSI EN 300 468, 6.2.42 |
|
|
ETSI EN 300 468, 6.2.43 |
|
|
ETSI EN 300 468, 6.2.13.4 |
|
|
ETSI EN 300 468, 6.2.44 |
|
|
ETSI EN 301 192, 9.5 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.93 |
|
|
ETSI TS 101 812, 10.8.1 |
|
|
ETSI EN 300 468, 6.2.46 |
|
|
ARIB STD-B10, Part 2, 6.2.42 |
|
|
ETSI EN 303 560, 5.2.1.1 |
|
|
ETSI TS 102 323, 11.2.4 |
|
|
ETSI TS 102 006, 9.5.2.6 |
|
|
ETSI TS 101 162 |
|
|
ETSI EN 300 468, 6.2.47 |
|
|
ETSI EN 300 468, 6.2.48 |
|
|
ARIB STD-B10, Part 2, 6.2.30 |
|
|
ETSI EN 300 468, 6.4.16 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.2 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.14 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.120 |
|
|
ETSI EN 300 468, 6.4.17 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.131 |
|
|
ISO/IEC 13818-1, ITU-T H.222.0, 2.6.129 |
|
|
ETSI TS 102 727, 10.17.6 |
|
|
ETSI TS 102 727, 10.17.3 |
Appendix B: Licenses
B.1. TSDuck license
TSDuck is released under the terms of the license which is commonly referred to as "BSD 2-Clause License" or "Simplified BSD License" or "FreeBSD License". See http://opensource.org/licenses/BSD-2-Clause.
Copyright © 2005-2026, Thierry Lelégard
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
-
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
-
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
B.2. Third-party libraries
TSDuck includes a few third-party libraries, either in source form, binary form
or both. For more details about the licenses of these third-party libraries,
see the file named OTHERS.txt in the TSDuck source code repository.
DTAPI: On Linux and Windows, the TSDuck binary distributions contain the DTAPI library in static form. This library is part of the Dektec SDK. This software is available in binary format only and is distributed under the BSD 2-Clause License. "Copyright (c) 2017 by Dektec Digital Video B.V."
LIBSRT: On Windows, the TSDuck binary distribution contains the SRT library in static form. There are two versions of the SRT library, one from Haivision, one from Robotweax. TSDuck can be built with any of them.
Haivision LIBSRT: The Haivision SRT library is open-source and distributed under the Mozilla Public License v2.0. "Copyright (c) 2018 Haivision Systems Inc."
Robotweax LIBSRT: The Robotweax SRT library is open-source and distributed under the MIT License. "Copyright (c) 2026 Robotweax GmbH and contributors"
LIBRIST: On Windows, the TSDuck binary distribution contains the RIST library in static form. This is an open-source library which is distributed under the BSD 2-Clause License. "Copyright © 2019-2020 SipRadius LLC. All right reserved."
LibVatek: On Windows and Linux, the TSDuck binary distribution contains the LibVatek library in static form. This is an open-source library which is distributed under the BSD 2-Clause License. "Copyright © 2022, Richie Chang."
Small Deflate (sdefl): On Windows, the TSDuck binary distribution contains the Small Deflate library in static form. This is an open-source library which is distributed under two licenses, MIT License and Public Domain License. "Copyright (c) 2020-2023 Micha Mettke."
Appendix C: References
Bibliography
-
[BSD-2C] BSD 2-Clause License, http://opensource.org/licenses/BSD-2-Clause
-
[Haivision-SRT] Haivision original SRT library, https://github.com/Haivision/srt/
-
[MEYERS-EFF] "Effective C++, Third Edition, 55 Specific Ways to Improve Your Programs and Designs", Scott Meyers, Addison-Wesley, 2005.
-
[MEYERS-MORE] "More Effective C++, 35 New Ways to Improve Your Programs and Designs", Scott Meyers, Addison-Wesley, 2008.
-
[MEYERS-STL] "Effective STL, 50 Specific Ways to Improve Your Use of the Standard Template Library", Scott Meyers, Addison-Wesley, 2001.
-
[Robotweax-SRT] Robotweax alternative SRT library, https://github.com/Robotweax/srt/
-
[STROUSTRUP] "The C++ Programming Language, Special Edition", Bjarne Stroustrup, Addison-Wesley, 2000.
-
[TSDuck] TSDuck Web site, https://tsduck.io/
-
[TSDuck-Issues] TSDuck issues tracker and discussion forum, https://github.com/tsduck/tsduck/issues
-
[TSDuck-Source] TSDuck source code repository, https://github.com/tsduck/tsduck/