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=1316

Key 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:

  1. Create the SCTE-35 Splice Info Section: Generate your target SCTE-35 binary command (e.g., a splice_insert cue-out for 30 seconds). Example binary payload in Base64: /DA0AAAAAAAA///wBQb+AAAAAAA6AhRDVUVJQAAA+3/PAAEBAAAALMvYAA==

  2. Send the command via Netcat (nc): To inject the marker immediately (using a 0 microsecond 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 -u if your scte35async filter 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.

  1. Prepare a timed metadata file containing your SCTE-35 binary packets.
  2. 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.ts

Note: 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.