How to Inject SCTE-35 Cues in FFmpeg
This article provides a practical guide on how to inject and manage SCTE-35 ad insertion cues in video streams using FFmpeg. You will learn how to configure the MPEG-TS muxer, allocate SCTE-35 Program Map Table (PMT) descriptors, and preserve or inject ad markers into your live and file-based video workflows.
Understanding SCTE-35 in FFmpeg
SCTE-35 is the industry standard for signaling cue tones, local ad insertion opportunities, and program splice points in MPEG Transport Streams (MPEG-TS).
FFmpeg does not feature a dedicated video filter (like
-vf) to dynamically generate SCTE-35 messages on the fly.
Instead, SCTE-35 is treated as a metadata or data stream. FFmpeg handles
SCTE-35 cues primarily at the muxer and demuxer level using the
mpegts container format.
To work with SCTE-35 in FFmpeg, you generally use one of three approaches: passing through existing cues, allocating an SCTE-35 PID for external injectors, or using timed metadata.
Method 1: Passing Through Existing SCTE-35 Cues
If your input stream (such as an SDI capture, UDP, or SRT stream) already contains SCTE-35 cues, you must instruct FFmpeg to copy the data stream to the output. By default, FFmpeg only maps video and audio streams.
To preserve and pass through existing SCTE-35 cues, use the stream
copy (-c:s copy or -c:d copy) and mapping
flags:
ffmpeg -i input.ts -map 0:v -map 0:a -map 0:s? -c:v libx264 -c:a aac -c:s copy -f mpegts output.ts-map 0:s?: Maps all data/subtitle streams (including SCTE-35) if they exist in the input. The?prevents the command from failing if no SCTE-35 stream is found.-c:s copy: Copies the data/subtitle stream packets without re-encoding them.
Method 2: Allocating an SCTE-35 PID in the MPEG-TS Muxer
If you are broadcasting a live stream to an downstream hardware or software splicer (like an edge transcoder or ad-splicer), you must reserve a specific Program Identifier (PID) for SCTE-35 cues in the PMT of the output MPEG-TS.
Use the -scte35_pid parameter inside the
mpegts muxer:
ffmpeg -i input.mp4 -c:v libx264 -c:a aac -f mpegts -scte35_pid 0x104 output.ts-f mpegts: Forces the output format to MPEG Transport Stream.-scte35_pid 0x104: Allocates PID260(decimal for0x104) specifically for SCTE-35 cue packets.
Downstream ad injectors can now target this specific PID to insert
splicing commands (splice_info_section) directly into your
stream.
Method 3: Injecting SCTE-35 via HLS (HTTP Live Streaming)
If your workflow involves packaging live streams into HLS (HTTP Live
Streaming) for OTT delivery, SCTE-35 cues must be translated into HLS
playlist tags, such as #EXT-X-DATERANGE or
#EXT-OATR-SCTE35.
FFmpeg’s HLS muxer can accept input SCTE-35 cues and convert them
into standard HLS tags using the -hls_flags parameter.
ffmpeg -i input_with_scte35.ts \
-c:v libx264 -c:a aac \
-f hls \
-hls_time 6 \
-hls_playlist_type event \
-hls_flags scte35_byteout+independent_segments \
playlist.m3u8scte35_byteout: Instructs the HLS muxer to write SCTE-35 cues as base64-encoded binary data within#EXT-X-DATERANGEtags in the.m3u8manifest.
Verifying SCTE-35 Cue Injection
To verify that your output stream contains the correct SCTE-35 PID
and data, use ffprobe to inspect the stream structure:
ffprobe -i output.ts -show_streamsLook for a stream entry with: * codec_type=data *
codec_name=scte_35
This confirms that the SCTE-35 stream is successfully provisioned and ready for your ad insertion workflow.