Insert Custom SCTE-35 Markers in MPEG-TS Using FFmpeg
This article provides a practical guide on how to inject custom
SCTE-35 digital cue markers into an MPEG-TS (MPEG Transport Stream)
using FFmpeg. We will cover the core command-line configurations,
explain how to utilize the scte35async filter to
dynamically inject cue-out and cue-in markers via network sockets, and
discuss how to format your payloads to ensure compatibility with
downstream ad insertion systems.
Understanding SCTE-35 in MPEG-TS
SCTE-35 is the industry-standard signaling protocol used to identify
splice points and advertisement opportunities in digital television and
video streams. In an MPEG-TS container, SCTE-35 cues are carried in
their own elementary stream, designated by the stream type
0x86 in the Program Map Table (PMT). To insert these
markers using FFmpeg, you must define an SCTE-35 stream and provide a
mechanism to deliver the splice commands.
Method
1: Dynamic Injection using the scte35async Filter
The most flexible way to write custom SCTE-35 markers into a live or
file-based MPEG-TS stream is by using FFmpeg’s scte35async
filter. This filter opens a local socket (UDP or TCP) that listens for
incoming SCTE-35 commands and multiplexes them into the output transport
stream on the fly.
1. The FFmpeg Command Syntax
To set up an FFmpeg process that listens for SCTE-35 payloads on a local port and injects them into an MPEG-TS output, use the following command structure:
ffmpeg -re -i input.mp4 \
-c:v libx264 -preset veryfast -b:v 3000k \
-c:a aac -b:a 128k \
-filter_complex "scte35async=port=9000:bind_address=127.0.0.1" \
-f mpegts -mpegts_flags +system_b \
udp://239.0.0.1:1234?pkt_size=1316Key Parameters Explained: *
-filter_complex "scte35async=port=9000": Instructs FFmpeg
to spin up an asynchronous SCTE-35 listener on port 9000. *
-mpegts_flags +system_b: Ensures proper packet alignment
and metadata output structure within the MPEG-TS container. *
udp://239.0.0.1:1234: The destination multicast or unicast
address of your output MPEG-TS stream containing the video, audio, and
newly created SCTE-35 PID.
2. Sending SCTE-35 Commands to the Socket
Once FFmpeg is running and listening on port 9000, you
must send formatted commands to trigger the cues. The
scte35async filter accepts simple string instructions
containing a Presentation Time Stamp (PTS) offset and the base64-encoded
SCTE-35 payload.
The payload format sent to the socket should follow this syntax:
<PTS_Offset_in_Microseconds> <Base64_Encoded_SCTE35_Command>
Step-by-Step Payload Generation:
Create the SCTE-35 Splice Info Section: Generate your target SCTE-35 binary command (e.g., a
splice_insertcue-out for 30 seconds). Example binary payload in Base64:/DA0AAAAAAAA///wBQb+AAAAAAA6AhRDVUVJQAAA+3/PAAEBAAAALMvYAA==Send the command via Netcat (nc): To inject the marker immediately (using a
0microsecond delay/offset relative to the current stream time), send the instruction to the open socket:echo "0 /DA0AAAAAAAA///wBQb+AAAAAAA6AhRDVUVJQAAA+3/PAAEBAAAALMvYAA==" | nc -u -w1 127.0.0.1 9000(Note: Use
-uif yourscte35asyncfilter is configured to listen over UDP).
Method 2: Static Inline Insertion Using Metadata (File-to-File)
If you are not working with a live stream and instead want to insert SCTE-35 markers at predefined, static timestamps during file transcodes, you can map pre-recorded SCTE-35 packet files directly into the MPEG-TS muxer.
- Prepare a timed metadata file containing your SCTE-35 binary packets.
- Run FFmpeg to map the metadata stream as an independent PID:
ffmpeg -i input.mp4 -i scte_markers.bin \
-map 0:v -map 0:a -map 1:s \
-c:v copy -c:a copy -c:s copy \
-streamid 2:258 \
-f mpegts output.tsNote: In this command, -streamid 2:258 manually maps
the third input stream (the SCTE-35 markers) to PID 258
within the transport stream, signaling it as a data/splice
stream.