[FFmpeg-devel] [PATCH v4 3/4] mpegtsenc: Add support for output of SCTE-35 streams over TS

Devin Heitmueller devin.heitmueller at ltnglobal.com
Mon Jul 31 16:38:05 EEST 2023


Introduce the ability to pass through SCTE-35 packets when creating
MPEG transport streams.  Note that this patch makes no effort to
change the PTS values in the SCTE-35 packets, and thus only works
when not reclocking the stream (i.e. using -copyts).  A subsequent
patch includes a BSF to recompute the PTS values.

Signed-off-by: Devin Heitmueller <dheitmueller at ltnglobal.com>
---
 libavformat/mpegts.h    |  1 +
 libavformat/mpegtsenc.c | 74 ++++++++++++++++++++++++++++++++++++++++++++++---
 libavformat/mux.c       |  6 ++--
 3 files changed, 75 insertions(+), 6 deletions(-)

diff --git a/libavformat/mpegts.h b/libavformat/mpegts.h
index a48f14e..a7aaaba 100644
--- a/libavformat/mpegts.h
+++ b/libavformat/mpegts.h
@@ -137,6 +137,7 @@
 #define STREAM_TYPE_AUDIO_AC3       0x81
 #define STREAM_TYPE_AUDIO_DTS       0x82
 #define STREAM_TYPE_AUDIO_TRUEHD    0x83
+#define STREAM_TYPE_SCTE_35         0x86
 #define STREAM_TYPE_AUDIO_EAC3      0x87
 
 /* ISO/IEC 13818-1 Table 2-22 */
diff --git a/libavformat/mpegtsenc.c b/libavformat/mpegtsenc.c
index 700fc54..c6cd1fd 100644
--- a/libavformat/mpegtsenc.c
+++ b/libavformat/mpegtsenc.c
@@ -425,6 +425,9 @@ static int get_dvb_stream_type(AVFormatContext *s, AVStream *st)
     case AV_CODEC_ID_SMPTE_2038:
         stream_type = STREAM_TYPE_PRIVATE_DATA;
         break;
+    case AV_CODEC_ID_SCTE_35:
+        stream_type = STREAM_TYPE_SCTE_35;
+        break;
     case AV_CODEC_ID_DVB_SUBTITLE:
     case AV_CODEC_ID_DVB_TELETEXT:
     case AV_CODEC_ID_ARIB_CAPTION:
@@ -522,6 +525,16 @@ static int mpegts_write_pmt(AVFormatContext *s, MpegTSService *service)
         *q++ = 0xfc;        // private_data_byte
     }
 
+    /* If there is an SCTE-35 stream, we need a registration descriptor
+       at the program level (SCTE 35 2016 Sec 8.1) */
+    for (i = 0; i < s->nb_streams; i++) {
+        AVStream *st = s->streams[i];
+        if (st->codecpar->codec_id == AV_CODEC_ID_SCTE_35) {
+            put_registration_descriptor(&q, MKTAG('C', 'U', 'E', 'I'));
+            break;
+        }
+    }
+
     val = 0xf000 | (q - program_info_length_ptr - 2);
     program_info_length_ptr[0] = val >> 8;
     program_info_length_ptr[1] = val;
@@ -533,6 +546,14 @@ static int mpegts_write_pmt(AVFormatContext *s, MpegTSService *service)
         const char default_language[] = "und";
         const char *language = lang && strlen(lang->value) >= 3 ? lang->value : default_language;
         enum AVCodecID codec_id = st->codecpar->codec_id;
+        uint16_t pid;
+
+        if (st->codecpar->codec_id == AV_CODEC_ID_SCTE_35) {
+            MpegTSSection *sect = st->priv_data;
+            pid = sect->pid;
+        } else {
+            pid = ts_st->pid;
+        }
 
         if (s->nb_programs) {
             int k, found = 0;
@@ -556,7 +577,7 @@ static int mpegts_write_pmt(AVFormatContext *s, MpegTSService *service)
         stream_type = ts->m2ts_mode ? get_m2ts_stream_type(s, st) : get_dvb_stream_type(s, st);
 
         *q++ = stream_type;
-        put16(&q, 0xe000 | ts_st->pid);
+        put16(&q, 0xe000 | pid);
         desc_length_ptr = q;
         q += 2; /* patched after */
 
@@ -819,6 +840,10 @@ static int mpegts_write_pmt(AVFormatContext *s, MpegTSService *service)
                 putbuf(&q, tag, strlen(tag));
                 *q++ = 0;            /* metadata service ID */
                 *q++ = 0xF;          /* metadata_locator_record_flag|MPEG_carriage_flags|reserved */
+            } else if (st->codecpar->codec_id == AV_CODEC_ID_SCTE_35) {
+                *q++ = 0x8a; /* Cue Identifier Descriptor */
+                *q++ = 0x01; /* length */
+                *q++ = 0x01; /* Cue Stream Type (see Sec 8.2) */
             }
             break;
         }
@@ -1159,6 +1184,33 @@ static int mpegts_init(AVFormatContext *s)
         AVStream *st = s->streams[i];
         MpegTSWriteStream *ts_st;
 
+        if (st->codecpar->codec_id == AV_CODEC_ID_SCTE_35) {
+            struct MpegTSSection *sect;
+            sect = av_mallocz(sizeof(MpegTSSection));
+            if (!sect) {
+                ret = AVERROR(ENOMEM);
+                continue;
+            }
+
+            if (st->id < 16) {
+                sect->pid = ts->start_pid + i;
+            } else if (st->id < 0x1FFF) {
+                sect->pid = st->id;
+            } else {
+                av_log(s, AV_LOG_ERROR,
+                       "Invalid stream id %d, must be less than 8191\n", st->id);
+                ret = AVERROR(EINVAL);
+                continue;
+            }
+
+            sect->write_packet = section_write_packet;
+            sect->opaque       = s;
+            sect->cc           = 15;
+            sect->discontinuity= ts->flags & MPEGTS_FLAG_DISCONT;
+            st->priv_data = sect;
+            continue;
+        }
+
         ts_st = av_mallocz(sizeof(MpegTSWriteStream));
         if (!ts_st) {
             return AVERROR(ENOMEM);
@@ -1877,6 +1929,19 @@ static int mpegts_write_packet_internal(AVFormatContext *s, AVPacket *pkt)
             dts += delay;
     }
 
+    if (st->codecpar->codec_id == AV_CODEC_ID_SCTE_35) {
+        MpegTSSection *s = st->priv_data;
+        uint8_t data[SECTION_LENGTH];
+
+        if (size > SECTION_LENGTH) {
+            av_log(s, AV_LOG_ERROR, "SCTE-35 section too long\n");
+            return AVERROR_INVALIDDATA;
+        }
+        memcpy(data, buf, size);
+        mpegts_write_section(s, data, size);
+        return 0;
+    }
+
     if (!ts_st->first_timestamp_checked && (pts == AV_NOPTS_VALUE || dts == AV_NOPTS_VALUE)) {
         av_log(s, AV_LOG_ERROR, "first pts and dts value must be set\n");
         return AVERROR_INVALIDDATA;
@@ -2149,7 +2214,8 @@ static int mpegts_write_packet_internal(AVFormatContext *s, AVPacket *pkt)
         return 0;
     }
 
-    if (ts_st->payload_size && (ts_st->payload_size + size > ts->pes_payload_size ||
+    if (st->codecpar->codec_id != AV_CODEC_ID_SCTE_35 &&
+        ts_st->payload_size && (ts_st->payload_size + size > ts->pes_payload_size ||
         (dts != AV_NOPTS_VALUE && ts_st->payload_dts != AV_NOPTS_VALUE &&
          dts - ts_st->payload_dts >= max_audio_delay) ||
         ts_st->opus_queued_samples + opus_samples >= 5760 /* 120ms */)) {
@@ -2194,7 +2260,7 @@ static void mpegts_write_flush(AVFormatContext *s)
     for (i = 0; i < s->nb_streams; i++) {
         AVStream *st = s->streams[i];
         MpegTSWriteStream *ts_st = st->priv_data;
-        if (ts_st->payload_size > 0) {
+        if (st->codecpar->codec_id != AV_CODEC_ID_SCTE_35 && ts_st->payload_size > 0) {
             mpegts_write_pes(s, st, ts_st->payload, ts_st->payload_size,
                              ts_st->payload_pts, ts_st->payload_dts,
                              ts_st->payload_flags & AV_PKT_FLAG_KEY, -1);
@@ -2237,7 +2303,7 @@ static void mpegts_deinit(AVFormatContext *s)
     for (i = 0; i < s->nb_streams; i++) {
         AVStream *st = s->streams[i];
         MpegTSWriteStream *ts_st = st->priv_data;
-        if (ts_st) {
+        if (ts_st && st->codecpar->codec_id != AV_CODEC_ID_SCTE_35) {
             av_freep(&ts_st->dvb_ac3_desc);
             av_freep(&ts_st->payload);
             if (ts_st->amux) {
diff --git a/libavformat/mux.c b/libavformat/mux.c
index 415bd39..55bec25 100644
--- a/libavformat/mux.c
+++ b/libavformat/mux.c
@@ -307,7 +307,8 @@ FF_ENABLE_DEPRECATION_WARNINGS
         }
 
         if (par->codec_type != AVMEDIA_TYPE_ATTACHMENT &&
-            par->codec_id != AV_CODEC_ID_SMPTE_2038)
+            par->codec_id != AV_CODEC_ID_SMPTE_2038 &&
+            par->codec_id != AV_CODEC_ID_SCTE_35)
             si->nb_interleaved_streams++;
     }
     si->interleave_packet = of->interleave_packet;
@@ -946,7 +947,8 @@ int ff_interleave_packet_per_dts(AVFormatContext *s, AVPacket *pkt,
         } else if (par->codec_type != AVMEDIA_TYPE_ATTACHMENT &&
                    par->codec_id != AV_CODEC_ID_VP8 &&
                    par->codec_id != AV_CODEC_ID_VP9 &&
-                   par->codec_id != AV_CODEC_ID_SMPTE_2038) {
+                   par->codec_id != AV_CODEC_ID_SMPTE_2038 &&
+                   par->codec_id != AV_CODEC_ID_SCTE_35) {
             ++noninterleaved_count;
         }
     }
-- 
1.8.3.1



More information about the ffmpeg-devel mailing list